Swiftorial Logo
Home
Swift Lessons
Tutorials
Learn More
Career
Resources

Strangler Pattern Architecture

Introduction to the Strangler Pattern

The Strangler Pattern is a proven strategy for incrementally modernizing legacy systems by gradually replacing functionality with new services. A Strangler Facade (or proxy) intercepts all incoming requests, routing them to either the Legacy System or new Modern Services based on feature migration status. This approach allows for zero-downtime migration, continuous delivery of new functionality, and eventual full decommissioning of the legacy system.

The Strangler Pattern enables risk-managed modernization by progressively routing features to new services while maintaining system stability.

Strangler Pattern Architecture Diagram

The diagram below illustrates the complete Strangler Pattern flow with proper routing through the facade. All client requests first hit the Strangler Facade (orange) which routes them based on migration status - either to Legacy System (red) or Modern Services (blue). The facade also handles response aggregation when needed. Synchronization between systems (green) maintains data consistency during migration.

graph TD A[Client] -->|All Requests| B[Strangler Facade] B -->|Legacy Feature| C[Legacy System] B -->|Modernized Feature| D[Modern Service A] B -->|Modernized Feature| E[Modern Service B] C -->|Legacy Response| B D -->|Modern Response| B E -->|Modern Response| B B -->|Aggregated Response| A F[Data Sync] -->|Replicates| C F -->|Updates| D F -->|Updates| E subgraph Client_Layer["Client Layer"] A end subgraph Facade_Layer["Facade Layer"] B end subgraph Legacy_System["Legacy System"] C end subgraph Modern_Services["Modern Services"] D E end subgraph Data_Synchronization["Data Sync"] F end classDef client fill:#ffeb3b,stroke:#ffeb3b,stroke-width:2px,rx:10,ry:10; classDef facade fill:#ff6f61,stroke:#ff6f61,stroke-width:2px,rx:5,ry:5; classDef legacy fill:#e74c3c,stroke:#e74c3c,stroke-width:2px,rx:5,ry:5; classDef modern fill:#3498db,stroke:#3498db,stroke-width:2px,rx:5,ry:5; classDef sync fill:#2ecc71,stroke:#2ecc71,stroke-width:2px,rx:5,ry:5; class A client; class B facade; class C legacy; class D,E modern; class F sync; linkStyle 0 stroke:#ffeb3b,stroke-width:2.5px; linkStyle 1 stroke:#e74c3c,stroke-width:2.5px; linkStyle 2,3 stroke:#3498db,stroke-width:2.5px; linkStyle 4 stroke:#e74c3c,stroke-width:2.5px; linkStyle 5,6 stroke:#3498db,stroke-width:2.5px; linkStyle 7 stroke:#ffeb3b,stroke-width:2.5px; linkStyle 8,9,10 stroke:#2ecc71,stroke-width:2.5px,stroke-dasharray:3,3;
The Strangler Facade serves as the single entry point, routing requests to appropriate systems while Data Sync maintains consistency during the migration period.

Key Components

The core architectural elements of the Strangler Pattern include:

  • Strangler Facade:
    • Single entry point for all client requests
    • Routes requests based on feature migration status
    • Handles protocol translation if needed
    • May aggregate responses from multiple systems
  • Legacy System:
    • Original monolithic application
    • Gradually reduced in scope as features migrate
    • Eventually decommissioned completely
  • Modern Services:
    • Newly developed microservices
    • Implement modernized features
    • Can use different technologies than legacy system
  • Data Synchronization:
    • Bi-directional data sync during migration
    • Eventual consistency model
    • Handles data format transformations

Benefits of the Strangler Pattern

  • Risk Mitigation:
    • Features can be migrated one at a time
    • Rollback is simple (just update routing)
    • No big-bang cutover
  • Continuous Delivery:
    • New features can be delivered incrementally
    • Allows for A/B testing of new implementations
  • Technology Flexibility:
    • New services can use modern tech stacks
    • Legacy system remains unchanged until replaced
  • Operational Stability:
    • Zero downtime migrations
    • Performance can be optimized per service

Implementation Considerations

Implementing the Strangler Pattern effectively requires addressing several key aspects:

  • Routing Strategy:
    • Implement feature flags for routing decisions
    • Consider canary releases for new implementations
    • Support gradual traffic migration
  • Data Management:
    • Implement bi-directional sync during transition
    • Handle schema differences between systems
    • Plan for eventual legacy data migration
  • Migration Planning:
    • Prioritize loosely coupled features first
    • Identify and migrate vertical slices of functionality
    • Create clear metrics for migration progress
  • Testing Approach:
    • Implement contract testing between facade and services
    • Verify data consistency across systems
    • Test failover scenarios
The Strangler Pattern works best when combined with Domain-Driven Design to identify clear bounded contexts for migration.

Example: Strangler Facade Implementation

Below is an enhanced Node.js implementation of a Strangler Facade with feature flags and response aggregation:

const express = require('express');
const axios = require('axios');
const app = express();

// Feature flags configuration
const featureFlags = {
  'user-profile': { modernized: true, modernService: 'http://user-service' },
  'order-processing': { modernized: false, legacyEndpoint: '/orders' },
  'inventory': { modernized: true, modernService: 'http://inventory-service' }
};

// Strangler Facade
app.all('/api/:feature/*', async (req, res) => {
  const { feature } = req.params;
  const flag = featureFlags[feature];
  
  if (!flag) {
    return res.status(404).json({ error: 'Feature not found' });
  }

  try {
    if (flag.modernized) {
      // Route to modern service
      const response = await axios({
        method: req.method,
        url: `${flag.modernService}${req.originalUrl.replace(`/api/${feature}`, '')}`,
        data: req.body,
        headers: { 'Content-Type': 'application/json' }
      });
      res.json(response.data);
    } else {
      // Route to legacy system
      const response = await axios({
        method: req.method,
        url: `http://legacy-system${flag.legacyEndpoint}${req.originalUrl.split(feature)[1]}`,
        data: req.body,
        headers: { 'Content-Type': 'application/json' }
      });
      res.json(response.data);
    }
  } catch (error) {
    console.error(`Error processing ${feature}:`, error.message);
    res.status(500).json({ error: 'Service unavailable' });
  }
});

// Data synchronization endpoint
app.post('/sync', handleDataSync);

app.listen(3000, () => console.log('Strangler Facade running on port 3000'));
                

Example: Data Synchronization Service

Implementation of a data synchronization service between legacy and modern systems:

const { Kafka } = require('kafkajs');

class DataSynchronizer {
  constructor() {
    this.kafka = new Kafka({
      clientId: 'data-sync-service',
      brokers: ['kafka:9092']
    });
    
    this.producer = this.kafka.producer();
    this.consumer = this.kafka.consumer({ groupId: 'data-sync-group' });
  }

  async start() {
    await this.producer.connect();
    await this.consumer.connect();
    
    // Listen for legacy system changes
    await this.consumer.subscribe({ topic: 'legacy-changes' });
    
    await this.consumer.run({
      eachMessage: async ({ topic, partition, message }) => {
        const change = JSON.parse(message.value.toString());
        await this.replicateToModernSystems(change);
      }
    });
  }

  async replicateToModernSystems(change) {
    // Transform legacy data format to modern format
    const transformed = this.transformData(change);
    
    // Publish to modern services
    await this.producer.send({
      topic: 'modern-updates',
      messages: [
        { value: JSON.stringify(transformed) }
      ]
    });
  }

  transformData(legacyData) {
    // Example transformation logic
    return {
      id: legacyData.record_id,
      ...legacyData.fields,
      metadata: {
        source: 'legacy-system',
        migratedAt: new Date().toISOString()
      }
    };
  }
}

module.exports = DataSynchronizer;