The Strangler Fig pattern isn’t about slowly killing off old code; it’s about building a new, more vibrant system around the old one until the old one is entirely bypassed and can be safely removed.

Let’s see this in action. Imagine a monolithic e-commerce application with a ProductService that handles everything from fetching product details to managing inventory. We want to replace this with a new, microservice-based approach.

Here’s a simplified view of the old monolith’s ProductService (conceptually):

// Monolith ProductService (Conceptual)
public class ProductService {

    // ... other methods ...

    public Product getProductDetails(String productId) {
        // 1. Fetch from legacy database
        LegacyProductData data = legacyDb.fetchProduct(productId);
        // 2. Apply legacy business logic for pricing, discounts, etc.
        LegacyPricingEngine.applyDiscounts(data);
        // 3. Format for response
        return mapToProduct(data);
    }

    public void updateInventory(String productId, int quantityChange) {
        // 1. Lock legacy inventory table
        legacyInventoryDb.lockInventory(productId);
        // 2. Update inventory in legacy database
        legacyInventoryDb.updateQuantity(productId, quantityChange);
        // 3. Commit transaction
        legacyInventoryDb.commit();
    }
}

Now, we want to introduce a new ProductMicroservice that will eventually take over. We’ll start by intercepting requests to getProductDetails.

The Strangler Facade:

We introduce a facade, often an API Gateway or a dedicated proxy layer, that sits in front of both the monolith and the new microservices. Initially, this facade just routes all requests to the monolith.

# API Gateway Configuration (Conceptual)
routes:
  - path: /products/**
    service: monolith_product_service

Step 1: Extract and Replace a Small Piece

We build a new microservice for getProductDetails. This new service will connect to the same legacy database but apply new logic.

// ProductDetailsMicroservice (Conceptual)
@RestController
@RequestMapping("/api/v2/products")
public class ProductDetailsController {

    @Autowired
    private ProductRepository productRepository; // Connects to legacy DB

    @GetMapping("/{productId}")
    public Product getProductDetails(@PathVariable String productId) {
        // 1. Fetch from legacy database (same as monolith)
        LegacyProductData data = productRepository.findById(productId);
        // 2. Apply NEW, modern pricing logic (e.g., a separate microservice)
        NewPricingService.calculatePrice(data);
        // 3. Format for response
        return mapToProduct(data);
    }
}

Step 2: Redirect Traffic for That Piece

We update the Strangler Facade to route requests for getProductDetails to our new microservice, while other ProductService calls (like updateInventory) still go to the monolith.

# API Gateway Configuration (Updated)
routes:
  - path: /products/{productId} # Specific route for details
    service: product_details_microservice
  - path: /products/** # Catch-all for other product endpoints
    service: monolith_product_service

Now, when a user requests product details, they hit the new microservice. Inventory updates still go to the monolith. The system is running with a mix of old and new.

Step 3: Gradually Replace More

We repeat this process. We might extract inventory management into its own microservice.

// InventoryMicroservice (Conceptual)
@RestController
@RequestMapping("/api/v2/inventory")
public class InventoryController {

    @Autowired
    private InventoryRepository inventoryRepository; // Connects to legacy DB

    @PostMapping("/adjust")
    public void adjustInventory(@RequestBody InventoryAdjustmentRequest request) {
        // 1. Acquire lock on legacy inventory (or better, a new distributed lock)
        inventoryRepository.lockItem(request.getProductId());
        // 2. Update inventory in legacy database
        inventoryRepository.updateQuantity(request.getProductId(), request.getQuantityChange());
        // 3. Commit transaction
        inventoryRepository.commit();
    }
}

And update the facade again:

# API Gateway Configuration (Further Updated)
routes:
  - path: /products/{productId}
    service: product_details_microservice
  - path: /inventory/adjust # New route for inventory
    service: inventory_microservice
  - path: /products/** # Still catch-all for any remaining product endpoints
    service: monolith_product_service

The key is that the Strangler Facade acts as the single entry point, intelligently directing traffic. Over time, more and more functionality is moved to new services.

The Mental Model:

The Strangler Fig pattern is fundamentally about incrementalism and risk reduction. Instead of a "big bang" rewrite, you build new capabilities alongside the old ones. The facade is the crucial piece that allows you to switch traffic without downtime. Each extracted piece becomes a smaller, more manageable, and more modern service. The monolith’s responsibilities shrink until it’s just a shell, or even completely gone.

The problem this solves is the immense risk, cost, and time associated with a full rewrite. By carving out pieces, you can:

  1. Reduce Risk: Test and deploy new services independently. If the new ProductDetailsMicroservice has a bug, you can quickly revert the facade rule to point back to the monolith.
  2. Deliver Value Sooner: Start seeing the benefits of modern architecture (scalability, independent deployment, technology diversity) on specific features, not just after the entire project is done.
  3. Modernize Incrementally: Adopt new technologies, databases, or architectural patterns for each new service without affecting the rest of the system.

The "strangling" happens because the new system’s functionality replaces the old system’s functionality, piece by piece. The facade ensures that external clients interact with the new functionality, effectively "strangling" the old paths.

The most surprising aspect is how little the client needs to know. If the facade handles URL mapping and versioning correctly, the client might not even realize they’re talking to different services, or that the underlying architecture has dramatically changed. The facade provides a stable, evolving interface.

The next concept to explore is how to handle data synchronization and consistency between the new microservices and the legacy database during the transition.

Want structured learning?

Take the full Monolith course →