Marcio Cunha

Domain Decoupling in Microservices with Strangler Fig and Reverse Proxy

Learn how to safely migrate monolithic legacy systems to microservices using the Strangler Fig pattern and dynamic routing through an adaptive reverse proxy.

Marcio Cunha•4 min
Also available in:EspañolPortuguês
Summary
  • The Strangler Fig pattern gradually replaces parts of an old monolithic system with new independent services.
  • The reverse proxy acts as an intelligent gateway that transparently redirects specific traffic routes to new microservices.
  • HTTP header-based routing strategies enable production load testing without impacting regular users.
  • Asynchronous data replication mitigates temporary transactional inconsistency issues between the monolith and new services.
  • Continuous monitoring of latency and error rates ensures immediate rollback capabilities in case of failures during migration.

The Legacy System Challenge and the Need for Change

Many companies grow using monolithic architectures, where all system code lives in a single large repository and runs as one unified application. In practice, this means an error in a secondary feature can crash the entire system, making updates slow and risky. The challenge arises when this monolith becomes too large to maintain, requiring a transition to microservices, which are small, independent services focused on specific tasks.

Replacing an old system all at once is a classic engineering mistake that usually fails catastrophically. It is the equivalent of changing an airplane engine while flying at cruising speed without being able to land. To avoid this catastrophe, modern software engineering has adopted incremental approaches that allow continuous architectural evolution without business interruption.

The Strangler Fig Pattern in Practice

The Strangler Fig pattern, inspired by a parasitic plant that wraps around trees in the forest until replacing them, proposes the gradual replacement of a legacy system. Instead of rewriting everything from scratch, the team builds new microservices alongside the old monolith and migrates one feature at a time. In practice, this means the old system keeps running and serving most customers while the new pieces take over specific responsibilities in isolation.

This process drastically reduces project risk because the impact of an error is restricted to the feature that was just migrated. If something goes wrong in the new service, the scope of the problem is small and easy to isolate. With each newly migrated feature, the legacy system shrinks organically until the old code can be completely shut down and discarded without pain.

The Role of the Adaptive Reverse Proxy

So the external world does not notice the system being rebuilt from within, a component called a reverse proxy is used, acting as an intelligent gatekeeper at the infrastructure entrance. It intercepts all incoming user requests and decides where to send them based on predefined rules. An adaptive reverse proxy goes beyond static routing, adjusting traffic destinations dynamically based on health metrics, load, and software versions.

When a client accesses the system to view their profile, for example, the reverse proxy checks whether the profile module has already been migrated to the new microservice. If the answer is yes, traffic is directed to the new modern API. Otherwise, the request continues to be forwarded to the legacy monolith, ensuring a completely seamless transition for those on the other side of the screen.

Implementing Dynamic Routing

At the infrastructure layer, tools like Nginx or Caddy can be configured to handle this traffic switching using rules based on URL paths or HTTP headers. Below is a practical Nginx configuration example using dynamic variables to direct traffic based on the presence of a testing cookie:

http {
upstream monolith {
server legacy-app.internal:8080;
}

upstream new_service {
server modern-service.internal:3000;
}

server {
listen 80;
server_name api.company.com;

location /api/v1/users {
# If the user has a beta tester cookie, route to the microservice
if ($cookie_beta_tester = "true") {
proxy_pass http://new_service;
break;
}

# Otherwise, default goes to the legacy monolith
proxy_pass http://monolith;
}
}
}

This code snippet demonstrates how to isolate a specific route for controlled production testing. In practice, this allows engineering teams to validate the behavior of the new microservice with a real percentage of actual users before flipping the switch permanently for the entire customer base.

Data Consistency and Migration Strategies

The biggest hurdle in microservices migrations is not the application code, but rather the shared database. In the monolith, all tables talk to each other freely, whereas in microservices, each service owns its isolated database. To solve this during the transition, real-time data replication or patterns like the Outbox Pattern are used to synchronize information between the legacy database and the new databases.

This synchronization ensures that while the monolith and microservice coexist, both have access to the necessary information to operate without corrupting the business state. When the migration of that feature is considered stable and mature, direct access to the legacy database is revoked, completing yet another step in the legacy system strangulation cycle.

Final Considerations

Decoupling legacy microservices using the Strangler Fig pattern combined with an adaptive reverse proxy turns a high-risk project into a controlled, iterative journey. Modern engineering demands resilience and continuous delivery capability without compromising current operations. By focusing on granular migrations, intelligent routing, and secure data synchronization, organizations can modernize their technical infrastructure sustainably, ensuring business stability and developer agility.