Previous Access Token Pattern Log Aggregation Next

Strangler Fig Pattern

๐ŸŒฟ Strangler Fig Pattern in .NET Core

The Strangler Fig Pattern is a software architecture strategy used to incrementally replace legacy systems. Inspired by the way a strangler fig tree grows around and eventually replaces its host, this pattern allows developers to gradually refactor or migrate parts of a monolithic application to a modern architecture like .NET Core.

๐ŸŒฑ The Strangler Fig pattern is a refactoring strategy for incrementally migrating a monolithic application to a new architecture, such as microservices. It is inspired by the strangler fig vine, which starts its life in the branches of a host tree, eventually grows around it, and replaces the host entirely. In software, this means building a new application around the existing monolith and replacing its functionality piece by piece until the monolith can be safely decommissioned.

This pattern avoids the risks and disruptions of a complete rewrite by allowing the old and new systems to coexist during the transition. A proxy or facade is used to route requests to either the legacy system or the new microservices based on which features have been migrated.

โš™๏ธ How the Strangler Fig pattern works

  • ๐Ÿงฑ Legacy System: You start with a monolithic or outdated application (e.g., ASP.NET Framework).
  • ๐Ÿช„ New Components: Build new functionality in a modern platform (e.g., ASP.NET Core) alongside the legacy system.
  • ๐Ÿ”€ Routing Layer: Use a routing mechanism (like an API Gateway or reverse proxy) to direct requests to either the legacy or new components.
  • ๐Ÿงน Incremental Migration: Gradually move features from the legacy system to the new platform until the old system is fully replaced.

The process is an iterative, phased approach.

  • Introduce a facade: A proxy is placed in front of the monolithic application. Initially, it routes all incoming requests to the monolith.
  • Identify and migrate a feature: Identify a self-contained functional area within the monolith to replace, such as product catalog management or user authentication. Build a new microservice that replicates this functionality.
  • Reroute traffic: The facade is updated to direct traffic for the migrated feature to the new microservice. Requests for all other features still go to the monolith.
  • Handle data synchronization: Often, both the new microservice and the monolith still need access to the same data during the transition. Use a mechanism like a message queue to synchronize data changes between the old and new data stores.
  • Iterate and retire: Repeat the process of identifying, migrating, and rerouting traffic for other features. As more functionality is moved to microservices, the monolith's responsibilities shrink.
  • Decommission the monolith: Once all the monolith's functionality has been migrated to the new services, the old application can be safely shut down, and the facade can be removed or reconfigured to communicate directly with the new services.

๐Ÿงฉ Example: E-commerce migration with .NET Core and YARP

This example uses YARP (Yet Another Reverse Proxy), a reverse proxy library for .NET, as the facade to route traffic between a legacy monolith and new .NET Core microservices.

1. The legacy monolith
Imagine a legacy ASP.NET MVC application with the following endpoints:
/products/list
/products/details/{id}
/cart
/checkout

2. The new .NET Core microservice
Create a new .NET Core Web API project to implement the new product catalog functionality.
csharp

// NewProductService/Controllers/ProductsController.cs
[ApiController]
[Route("[controller]")]
public class ProductsController : ControllerBase
{
    [HttpGet("list")]
    public IActionResult GetProductsList() => Ok("New product list from microservice");
    
    [HttpGet("details/{id}")]
    public IActionResult GetProductDetails(int id) => Ok($"New product details for {id} from microservice");
}

3. The YARP facade (WebProxy.Api)
Create a new ASP.NET Core project and configure YARP to act as the reverse proxy.

Install NuGet package
shell

dotnet add package Yarp.ReverseProxy

Configure YARP in appsettings.json

 //Json
{
  "ReverseProxy": {
    "Routes": {
      "productsRoute": {
        "ClusterId": "newProductsCluster",
        "Match": {
          "Path": "/products/{**catch-all}"
        }
      },
      "legacyRoute": {
        "ClusterId": "legacyCluster",
        "Match": {
          "Path": "{**catch-all}"
        }
      }
    },
    "Clusters": {
      "newProductsCluster": {
        "Destinations": {
          "newProductsDestination": {
            "Address": "https://localhost:7001" // New microservice URL
          }
        }
      },
      "legacyCluster": {
        "Destinations": {
          "legacyDestination": {
            "Address": "https://localhost:5001" // Legacy monolith URL
          }
        }
      }
    }
  }
}

Configure Program.cs

//csharp
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddReverseProxy().LoadFromConfig(builder.Configuration.GetSection("ReverseProxy"));
var app = builder.Build();
app.MapReverseProxy();
app.Run();

In this setup, requests starting with /products/ are directed to the new microservice, while all other requests are sent to the legacy monolith.

โœ… Advantages

  • Reduced risk: Mitigates the risk of a catastrophic failure associated with a "big-bang" rewrite.
  • Business continuity: The existing system remains operational during the migration, ensuring no disruption to users.
  • Incremental value: You can deliver value incrementally by focusing on high-priority or problematic features first.
  • Technology flexibility: Allows for adopting modern technologies and architectures (like microservices) one piece at a time.
  • Independent scaling: New microservices can be scaled independently of the monolith.

โš ๏ธ Disadvantages

  • Increased complexity: The coexistence of two systems adds complexity, especially around routing and data synchronization.
  • Data consistency challenges: Keeping data synchronized between the new and old systems can be complex and may introduce eventual consistency issues.
  • Long transition period: The migration can take a long time, leading to prolonged maintenance of two systems and potential "hybrid" system overhead.
  • Proxy complexity and failure: The facade or proxy layer can become a single point of failure or performance bottleneck if not designed and managed robustly.
  • Technical debt accumulation: If not managed properly, residual technical debt or shortcuts taken during migration can transfer to the new system.

๐Ÿ•’ When to use

  • Large, complex monoliths: The pattern is particularly suitable for modernizing large legacy systems where a full rewrite is too risky or expensive.
  • Gradual modernization: When you want to modernize piece by piece and benefit from early wins.
  • Business continuity is critical: When the business cannot tolerate significant downtime during a migration.
  • Incremental feature delivery: When you need to deliver new features while simultaneously refactoring legacy code.

๐Ÿšซ When not to use

  • Small, simple systems: For small applications, a full rewrite might be simpler and more cost-effective.
  • Tight deadlines: The pattern is a long-term strategy and may not be suitable for projects with aggressive, short-term deadlines.
  • Untenable proxying: When requests to the backend system cannot be easily intercepted or redirected due to technical constraints.

โš ๏ธ Challenges

  • ๐Ÿ”— Requires careful integration between old and new systems.
  • ๐Ÿงญ Routing complexity can increase.
  • ๐Ÿ› ๏ธ Duplicate logic may exist temporarily.

๐Ÿ’ก Best practices and tips

  • Use an API gateway : A robust API gateway or reverse proxy like YARP, Ocelot, or a cloud provider's gateway is the cornerstone of the pattern in modern cloud applications.
  • Identify clear boundaries: Invest time in identifying the natural boundaries for a feature to be extracted. Domain-driven design (DDD) techniques can help in this discovery process.
  • Prioritize based on value: Start with a feature that provides high business value or is easy to extract to gain experience and build confidence.
  • Address data synchronization early: Have a solid plan for data synchronization between the old and new systems. Event-based mechanisms with message queues are a common approach.
  • Implement an anti-corruption layer: If a newly created microservice needs to call back into the monolith, implement an anti-corruption layer (ACL) to translate the calls and prevent contamination from the legacy codebase.
  • Automate CI/CD: Robust and automated CI/CD pipelines are crucial for managing and deploying the proxy and microservices independently.

๐Ÿ”Ž Precautions

  • Monitor the facade: Keep a close eye on the performance and reliability of your facade, as it can become a bottleneck.
  • Plan the decommissioning: Have a clear plan and timeline for eventually retiring the legacy system to avoid maintaining a hybrid system indefinitely.
  • Manage team skills: Ensure the team has the necessary skills to manage both the legacy codebase and the new technologies used for the microservices.
  • Test thoroughly: Implement comprehensive testing strategies, including end-to-end testing, to ensure seamless integration and functionality between the old and new systems.
Back to Index
Previous Access Token Pattern Log Aggregation Next
*