Strangler Fig — Legacy Migration Strategy

Incrementally Modernizing Integration Systems


Introduction

The Strangler Fig pattern is a migration strategy that allows you to gradually replace a legacy system with a modern one without requiring a "big bang" cutover. Named after the strangler fig tree that grows around and eventually replaces its host tree, this pattern enables continuous migration, reducing risk and allowing teams to validate each step. For Azure integration workloads, this is particularly valuable when modernizing ASMX web services, BizTalk orchestrations, or legacy message brokers.

This comprehensive guide covers:

  • Pattern fundamentals — Understanding strangler fig
  • Migration strategies — Incrementally replacing components
  • Azure implementation — Using API Management, Functions
  • Data migration — Handling state and migrations
  • Testing and validation — Ensuring quality during migration

Understanding the Pattern

How Strangler Fig Works

┌─────────────────────────────────────────────────────────────────────┐
│                  STRANGLER FIG PATTERN                              │
├─────────────────────────────────────────────────────────────────────┤
│                                                                     │
│   BEFORE: Monolithic Legacy                                         │
│   ───────────────────────────────────                               │
│                                                                     │
│   ┌─────────────────────────────────────────────────────────────┐   │
│   │                     LEGACY SYSTEM                           │   │
│   │                                                             │   │
│   │   ┌──────────┐  ┌──────────┐  ┌──────────┐  ┌──────────┐    │   │
│   │   │  Order   │  │Customer  │  │Inventory │  │ Shipping │    │   │
│   │   │ Service  │  │ Service  │  │ Service  │  │ Service  │    │   │
│   │   └──────────┘  └──────────┘  └──────────┘  └──────────┘    │   │
│   │                                                             │   │
│   └─────────────────────────────────────────────────────────────┘   │
│                                                                     │
│   DURING: Strangler in Action                                       │
│   ─────────────────────────────                                     │
│                                                                     │
│   ┌─────────────┐      ┌─────────────┐                              │
│   │   LEGACY    │      │   MODERN    │                              │
│   │   SYSTEM    │      │   SYSTEM    │                              │
│   │             │      │             │                              │
│   │ ┌─────────┐ │      │ ┌─────────┐ │                              │
│   │ │Customer │ │      │ │Inventory│ │                              │
│   │ │ Service │ │      │ │ Service │ │                              │
│   │ └─────────┘ │      │ └─────────┘ │                              │
│   │             │      │             │                              │
│   │ ┌─────────┐ │      │ ┌─────────┐ │                              │
│   │ │Shipping │ │      │ │  Order  │ │                              │
│   │ │ Service │ │      │ │ Service │ │                              │
│   │ └─────────┘ │      │ └─────────┘ │                              │
│   └──────┬──────┘      └──────┬──────┘                              │
│          │                    │                                     │
│          └──────────┬─────────┘                                     │
│                     ▼                                               │
│          ┌─────────────────────┐                                    │
│          │   API MANAGEMENT    │  ← Facade Layer                    │
│          │   (Strangler)       │                                    │
│          └─────────────────────┘                                    │
│                                                                     │
│   AFTER: Modern System                                              │
│   ─────────────────────                                             │
│                                                                     │
│   ┌─────────────────────────────────────────────────────────────┐   │
│   │                     MODERN SYSTEM                           │   │
│   │                                                             │   │
│   │   ┌──────────┐  ┌──────────┐  ┌──────────┐  ┌──────────┐    │   │
│   │   │  Order   │  │Customer  │  │Inventory │  │ Shipping │    │   │
│   │   │ Service  │  │ Service  │  │ Service  │  │ Service  │    │   │
│   │   │ (Azure   │  │ (Azure   │  │ (Azure   │  │ (Azure   │    │   │
│   │   │Functions)│  │Functions)│  │Functions)│  │Functions)│    │   │
│   │   └──────────┘  └──────────┘  └──────────┘  └──────────┘    │   │
│   │                                                             │   │
│   └─────────────────────────────────────────────────────────────┘   │
│                                                                     │
└─────────────────────────────────────────────────────────────────────┘

Migration Phases

┌─────────────────────────────────────────────────────────────────────┐
│                  MIGRATION PHASES                                   │
├─────────────────────────────────────────────────────────────────────┤
│                                                                     │
│   PHASE 1: FACADE LAYER                                             │
│   ────────────────────                                              │
│   Deploy API Management as the entry point                          │
│   Route traffic to legacy (100%) and modern (0%)                    │
│   Establish monitoring baseline                                     │
│                                                                     │
│   PHASE 2: EXTRACT FIRST SERVICE                                    │
│   ─────────────────────────────                                     │
│   Build modern service alongside legacy                             │
│   Route 5% traffic to modern, 95% to legacy                         │
│   Validate behavior and performance                                 │
│                                                                     │
│   PHASE 3: INCREASE TRAFFIC                                         │
│   ────────────────────────                                          │
│   Gradually shift to 50%, then 80%, then 95%                        │
│   Monitor error rates, latency, and user feedback                   │
│   Stop legacy service when confidence is high                       │
│                                                                     │
│   PHASE 4: REPEAT                                                   │
│   ─────────────────                                                 │
│   Move to next service                                              │
│   Continue until entire system is modern                            │
│                                                                     │
│   PHASE 5: DECOMMISSION                                             │
│   ──────────────────────                                            │
│   Remove legacy infrastructure                                      │
│   Clean up routing rules                                            │
│   Archive documentation                                             │
│                                                                     │
└─────────────────────────────────────────────────────────────────────┘

Azure Implementation

API Management Configuration

{
  "apiManagement": {
    "name": "integration-strangler",
    "resourceGroup": "rg-migration",
    "publisherEmail": "platform@company.com",
    "sku": "Developer",
    "apis": [
      {
        "name": "orders-api",
        "displayName": "Orders API",
        "backendService": {
          "url": "{{legacy-orders-url}}"
        },
        "policies": [
          {
            "name": "route-to-modern",
            "conditions": [
              {"expression": "{{enable-modern-orders}} == true"}
            ],
            "actions": [
              {"set-backend-service": {"url": "{{modern-orders-url}}"}}
            ]
          }
        ]
      }
    ],
    "versioning": {
      "scheme": "Header",
      "headerName": "x-api-version"
    }
  }
}

Traffic Routing Policy

<!-- API Management Routing Policy -->
<policies>
    <inbound>
        <!-- Feature flag based routing -->
        <choose>
            <when condition="@(context.Variables.GetValueOrDefault<bool>("useModernService"))">
                <set-backend-service base-url="https://modern-functions.azurewebsites.net" />
                <set-header name="X- routing-Reason" exists-action="override" value="modern-service" />
            </when>
            <otherwise>
                <set-backend-service base-url="https://legacy-api.azurewebsites.net" />
                <set-header name="X- routing-Reason" exists-action="override" value="legacy-service" />
            </otherwise>
        </choose>

        <!-- Correlation for tracing -->
        <set-header name="X-Correlation-ID" exists-action="override" value="@(Guid.NewGuid().ToString())" />
    </inbound>

    <backend>
        <!-- Circuit breaker for modern service -->
        <forward-request timeout="30" />
    </backend>

    <outbound>
        <!-- Ensure consistent response format -->
        <choose>
            <when condition="@(context.Variables.GetValueOrDefault<string>("routingReason") == "legacy-service")">
                <!-- Transform legacy response to modern format -->
                <json-transformer />
            </when>
        </choose>
    </outbound>
</policies>

Feature Flag Integration

public class FeatureFlagService
{
    private readonly IConfiguration _configuration;

    public bool IsModernServiceEnabled(string serviceName)
    {
        var featureKey = $"features:modern:{serviceName}";
        return _configuration.GetValue<bool>(featureKey);
    }

    public async Task<(string BackendUrl, bool UseModern)> GetRouteTargetAsync(
        string serviceName)
    {
        var useModern = IsModernServiceEnabled(serviceName);

        if (useModern)
        {
            var modernUrl = await GetModernServiceUrlAsync(serviceName);
            return (modernUrl, true);
        }

        var legacyUrl = await GetLegacyServiceUrlAsync(serviceName);
        return (legacyUrl, false);
    }
}

// Usage in Azure Function
public class OrdersFunction
{
    private readonly FeatureFlagService _featureFlags;

    public async Task<IActionResult> GetOrders(HttpRequest req)
    {
        var (url, isModern) = await _featureFlags.GetRouteTargetAsync("orders");

        // Call appropriate backend
        var response = await _httpClient.GetAsync(url);

        return new OkObjectResult(await response.Content.ReadAsStringAsync());
    }
}

Data Migration Strategy

Parallel Data Stores

public class ParallelDataMigration
{
    private readonly TableClient _legacyTable;
    private readonly CosmosClient _modernCosmos;

    public async Task MigrateDataIncrementalAsync(
        DateTime lastMigrateTime,
        CancellationToken ct)
    {
        // Read from legacy
        var legacyRecords = await _legacyTable.QueryAsync<LegacyOrder>(
            filter: $"Timestamp gt '{lastMigrateTime}'")
            .ToListAsync(ct);

        // Transform to modern format
        var modernOrders = legacyRecords.Select(TransformToModern).ToList();

        // Write to modern
        foreach (var order in modernOrders)
        {
            await _modernCosmos.CreateItemAsync("orders", order);
        }

        // Update checkpoint
        await UpdateMigrationCheckpointAsync(DateTime.UtcNow);
    }

    private Order TransformToModern(LegacyOrder legacy)
    {
        return new Order
        {
            Id = legacy.RowKey,
            CustomerId = legacy.PartitionKey,
            Items = JsonSerializer.Deserialize<List<OrderItem>>(legacy.ItemsJson),
            Total = legacy.TotalAmount,
            Status = MapStatus(legacy.Status),
            CreatedAt = legacy.Timestamp,
            ModifiedAt = legacy.LastModified
        };
    }
}

Dual-Write Pattern

public class DualWriteService
{
    private readonly ILegacyRepository _legacyRepo;
    private readonly IModernRepository _modernRepo;

    public async Task CreateOrderAsync(Order order)
    {
        // Write to both systems during migration
        var tasks = new List<Task>
        {
            _legacyRepo.CreateAsync(order),
            _modernRepo.CreateAsync(order)
        };

        try
        {
            await Task.WhenAll(tasks);
        }
        catch (Exception ex)
        {
            // Handle partial failure
            await HandleDualWriteFailureAsync(ex, order);
            throw;
        }
    }

    private async Task HandleDualWriteFailureAsync(Exception ex, Order order)
    {
        // If one succeeded, schedule reconciliation
        if (_legacyRepo.Exists(order.Id) != _modernRepo.Exists(order.Id))
        {
            await _reconciliationQueue.SendAsync(new ReconciliationMessage
            {
                EntityType = "Order",
                EntityId = order.Id,
                FailureTime = DateTime.UtcNow
            });
        }
    }
}

Testing Strategy

Validation Approach

public class MigrationValidator
{
    public async Task ValidateMigrationAsync(string entityType, string entityId)
    {
        var legacyEntity = await GetLegacyEntityAsync(entityType, entityId);
        var modernEntity = await GetModernEntityAsync(entityType, entityId);

        // Compare key fields
        Assert.Equal(legacyEntity.Id, modernEntity.Id);
        Assert.Equal(legacyEntity.CustomerId, modernEntity.CustomerId);
        Assert.Equal(legacyEntity.Total, modernEntity.Total);
        
        // Allow for transformation differences
        // (e.g., date formats, field naming)
    }

    public async Task<bool> ValidateTrafficShiftAsync(
        string serviceName,
        decimal expectedModernPercentage)
    {
        var requestCount = await GetRequestCountsAsync(serviceName);

        var actualPercentage = requestCount.ModernRequests / 
            (requestCount.ModernRequests + requestCount.LegacyRequests);

        // Allow 5% variance
        return Math.Abs(actualPercentage - expectedModernPercentage) < 0.05m;
    }

    public async Task<List<string>> IdentifyDataGapsAsync(string entityType)
    {
        var legacyIds = await GetAllLegacyIdsAsync(entityType);
        var modernIds = await GetAllModernIdsAsync(entityType);

        return legacyIds.Except(modernIds).ToList();
    }
}

Shadow Mode Testing

{
  "shadowTesting": {
    "enabled": true,
    "targetService": "orders-api",
    "mirrorTraffic": true,
    "comparison": {
      "responseTime": {
        "tolerance": "20%"
      },
      "responseBody": {
        "strictEquality": false,
        "fieldComparison": ["id", "customerId", "total", "items"]
      },
      "statusCodes": {
        "mustMatch": true
      }
    },
    "alerting": {
      "divergenceThreshold": "5%",
      "alertChannel": "slack-migration-alerts"
    }
  }
}

Monitoring During Migration

Key Metrics

┌─────────────────────────────────────────────────────────────────────┐
│                  MIGRATION METRICS DASHBOARD                        │
├─────────────────────────────────────────────────────────────────────┤
│                                                                     │
│   TRAFFIC SPLIT:                                                    │
│   • Legacy: 80% (target: decreasing)                                │
│   • Modern: 20% (target: increasing)                                │
│                                                                     │
│   HEALTH METRICS:                                                   │
│   • Legacy Error Rate: 0.1%                                         │
│   • Modern Error Rate: 0.05%                                        │
│   • Legacy Latency (p95): 150ms                                     │
│   • Modern Latency (p95): 45ms                                      │
│                                                                     │
│   DATA MIGRATION:                                                   │
│   • Orders Migrated: 45,000 / 50,000 (90%)                          │
│   • Last Migration Time: 2024-01-15 14:30 UTC                       │
│   • Data Gap Count: 3                                               │
│                                                                     │
│   ALERTS:                                                           │
│   ⚠ Modern service latency spike detected (resolved)                │
│   ✓ Data reconciliation completed                                   │
│                                                                     │
└─────────────────────────────────────────────────────────────────────┘

Best Practices

Implementation Checklist

PracticeDescription
Start with stable servicesDon't migrate complex/brittle services first
Feature flags everywhereEnable quick rollbacks
Monitor both pathsFull visibility during migration
Data validation earlyVerify data integrity before traffic shift
Parallel run periodRun both systems simultaneously
Document transformsCapture all mapping logic

Common Pitfalls

┌─────────────────────────────────────────────────────────────────────┐
│                  MIGRATION PITFALLS                                 │
├─────────────────────────────────────────────────────────────────────┤
│                                                                     │
│   ✗ Migrating too much at once                                      │
│   ✗ Not testing data transformation thoroughly                      │
│   ✗ Ignoring performance differences                                │
│   ✗ Failing to involve legacy system owners                         │
│   ✗ Skipping the parallel run phase                                 │
│   ✗ Not having rollback plan                                        │
│                                                                     │
└─────────────────────────────────────────────────────────────────────┘

Related Topics


Azure Integration Hub - Architect Level Enterprise Integration Patterns