Modernizing via Strangler Fig Pattern
How to safely migrate from a monolithic ASP.NET MVC app to modern microservices using the Strangler Fig Pattern and YARP (Yet Another Reverse Proxy).
When I first inherited a legacy ASP.NET MVC monolith with over 200 controllers and a single shared SQL Server database, every instinct told me to rewrite it from scratch. I spent two weeks drafting an ambitious migration plan with a new .NET 10 microservices architecture, estimated at six months. My tech lead looked at it and said, “We cannot freeze features for six months. Find a way to do this incrementally.” That conversation led me to the Strangler Fig pattern and YARP, and it completely changed my approach to legacy modernization. We have been migrating routes one at a time for over a year now, and the monolith shrinks steadily without ever disrupting the business.
Introduction
Rewriting a legacy system from scratch (the “Big Bang” approach) is almost always a mistake. It takes years, business features freeze, and the cutover day is a high-risk nightmare.
The Strangler Fig Pattern, named after the vine that grows around a host tree until it eventually replaces it, offers a safer alternative. You incrementally replace specific functionality with new services while the old system continues to run.
[Strangler Fig Application] — Martin Fowler , 2019-06-18Why Use Strangler Fig:
- Risk Reduction: You migrate one route at a time. If it fails, you just revert the route config.
- Immediate Value: You deliver new features in the new stack immediately, rather than waiting for a full rewrite.
- Coexistence: The user sees one unified application, unaware that the backend is split.
What We’ll Build
We will set up a Facade using YARP (Yet Another Reverse Proxy) in .NET. This proxy will sit in front of both our Legacy Monolith and our New Microservice, routing traffic intelligently.
Architecture Overview
The key component is the “Facade” or Proxy. It intercepts all incoming traffic.
[YARP Documentation] — Microsoft , 2024-05-15flowchart TD
User[Clients/Users] --> Proxy[YARP Proxy Facade]
Proxy -->|Match /api/v1/orders| New[New Service\n(.NET 10)]
Proxy -->|Everything else| Old[Legacy Monolith\n(ASP.NET MVC)]
classDef primary fill:#7c3aed,color:#fff
classDef secondary fill:#06b6d4,color:#fff
classDef db fill:#f43f5e,color:#fff
classDef warning fill:#fbbf24,color:#000
class User,Old warning
class Proxy secondary
class New primary
Section 1: Setting up YARP
YARP is an incredibly high-performance reverse proxy library from Microsoft. It fits perfectly into a standard ASP.NET Core application.
First, create a new empty Web API project for the proxy and install YARP.
dotnet add package Yarp.ReverseProxy
[YARP Getting Started]
— Microsoft YARP Team , 2024-05-15
Section 2: Configuration (The Route Map)
The magic of the Strangler Fig happens in appsettings.json. We define Clusters (destinations) and Routes (matching rules).
{
"ReverseProxy": {
"Routes": {
"orders-route": {
"ClusterId": "new-orders-service",
"Match": {
"Path": "/api/v1/orders/{**remainder}"
},
"Transforms": [
{ "PathPattern": "/orders/{**remainder}" }
]
},
"legacy-catch-all": {
"ClusterId": "legacy-monolith",
"Match": {
"Path": "{**catch-all}"
}
}
},
"Clusters": {
"new-orders-service": {
"Destinations": {
"destination1": {
"Address": "http://orders-service:8080"
}
}
},
"legacy-monolith": {
"Destinations": {
"destination1": {
"Address": "http://legacy-app:80"
}
}
}
}
}
}
In this configuration:
- Traffic to
/api/v1/orders/*is routed to the new separate service. - ALL other traffic falls through to the legacy monolith.
Section 3: The Program.cs
Wiring it up is simple.
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddReverseProxy()
.LoadFromConfig(builder.Configuration.GetSection("ReverseProxy"));
var app = builder.Build();
app.MapReverseProxy();
app.Run();
Section 4: Dealing with Database State
Often, the new service needs data that the legacy service owns. You have a few options:
- Shared Database (Anti-Pattern but Pragmatic): Both services talk to the same DB. Dangerous due to locking and schema coupling, but good for initial phases.
- Double Write: The proxy or client writes to both. Complicated error handling.
- Synchronization (CDC): Use Debezium or similar to sync data from Legacy DB to New DB asynchronously.
For the Strangler Pattern, we typically start with a Shared Database and slowly peel off tables into the new service’s exclusive domain as we refactor.
[Strangler Fig Pattern] — Microsoft , 2024-03-20 [Monolith to Microservices] — Sam Newman , 2019-11-14Conclusion
The Strangler Fig pattern allows you to modernize “in-flight”. By using YARP, you gain a robust, .NET-native way to manage this traffic routing dynamically.
Eventually, as you “strangle” more routes, the Monolith becomes small enough to be decommissioned entirely (or kept as a small service for truly dead-end features).
Having lived through this migration for over a year, my strongest piece of advice is to resist the temptation to migrate everything at once. Start with the route that causes the most pain — the one with the worst performance, the most bugs, or the most frequent feature requests — and use it as your proving ground. That first successful migration builds team confidence and establishes the operational patterns (health checks, feature flags, shared auth, database boundaries) that make every subsequent migration faster and safer. The monolith did not grow overnight, and it does not need to disappear overnight either.
[Building Evolutionary Architectures] — Neal Ford, Rebecca Parsons, and Patrick Kua , 2017-10-05Next Steps
- Learn about [Distributed Tracing] to track requests as they jump between Proxy -> Service -> Monolith.
- Read about [Identity Federation] to unify auth across the stack.