Strangler Fig Migrations Succeed When You Engineer Routing First
Most strangler fig failures do not come from bad domain boundaries or weak refactoring discipline. They come from a routing layer that was treated as plumbing, then asked to carry policy, observability, rollback, and consistency under production load.
Nesqual Tech AI
A retailer moved 18% of checkout traffic to a new order service and saw conversion drop 3.7% in 40 minutes. The new code was fine. The failure sat in the routing layer: sticky sessions broke, retry policy duplicated writes, and logs could not tell which path handled which customer. That pattern shows up more often than teams admit.
The strangler fig pattern works. By 2026, most enterprise modernization programs use some form of it because it reduces blast radius and lets you ship value before the full rewrite is done. The part nobody plans for is the routing layer: the control point that decides which request goes where, under what policy, with which identity, and how you roll back when reality disagrees with your migration plan.
Why the routing layer becomes the real migration platform
Teams usually diagram the old monolith, the new service, and a few APIs between them. Then they treat routing as a reverse proxy rule or an ingress object. That is the first mistake.
In a strangler fig migration, routing is not just traffic forwarding. It becomes the runtime contract for:
- request classification n- canary and percentage rollouts
- user or tenant affinity
- auth propagation and policy enforcement
- timeout, retry, and circuit-breaker behavior
- observability and auditability
- rollback speed
If you do not design those concerns upfront, the migration stalls after the first few cutovers.
Example: the "simple" customer profile extraction
Consider a B2B SaaS platform splitting customer profile reads from a Java monolith into a Go service. On paper, this looks low risk because reads are idempotent and the schema is stable.
What actually happens in production:
- Internal users need the new service first.
- Premium tenants need lower latency than long-tail tenants.
- A legacy mobile app still depends on a response field the new service no longer returns.
- Some requests must stay on the monolith because their auth token lacks the new audience claim.
That logic lives in routing. If it is scattered across CDN rules, API gateway plugins, service mesh policies, and application code, you lose control fast.
A healthy target in 2026 is to keep routing decisions observable and centralized by policy, even if execution is distributed. In practice, that often means an API gateway such as Kong Gateway 3.x, Apigee X, NGINX Plus, or Envoy Gateway, paired with a service mesh like Istio 1.24+ or Linkerd 2.16 for east-west traffic.
Design routing around decisions, not URLs
Most migration plans start with path-based routing: /api/customers/* goes to the new service. That works for demos. It breaks for real cutovers because migration decisions are rarely based only on URLs.
You need a routing model that supports multiple dimensions:
- path and method
- tenant ID
- user cohort
- geography
- client version
- feature flag state
- request risk level
- write versus read semantics
Build a routing decision matrix
Before you move a single endpoint, create a matrix that answers four questions for each request class:
- Who should handle it right now?
- What policy applies on the way there?
- How do you prove the decision was correct?
- How do you reverse it in under 5 minutes?
A practical matrix for an order domain might look like this:
routes:
- name: order-read-v2
match:
path_prefix: /api/orders/
methods: [GET]
headers:
x-tenant-tier: enterprise
x-client-version: ">=2026.2"
destination:
primary: order-query-service
fallback: monolith
policy:
timeout_ms: 250
retries: 1
retry_on: gateway-error,connect-failure
shadow_to: monolith
observability:
route_label: order-read-v2
sample_rate: 0.25
- name: order-write-legacy
match:
path_prefix: /api/orders
methods: [POST, PUT, PATCH]
destination:
primary: monolith
policy:
timeout_ms: 1200
retries: 0
observability:
route_label: order-write-legacy
This does two useful things. First, it separates routing intent from implementation detail. Second, it forces you to think about policy and rollback before traffic moves.
Keep write paths conservative longer than you want
Read traffic is forgiving. Write traffic exposes every hidden assumption in your routing layer.
A common 2026 benchmark for API gateways under enterprise workloads is p95 proxy latency of 3-8 ms for straightforward header and path matching, and 8-20 ms when custom auth, body inspection, or external policy checks are involved. That overhead is acceptable for reads. For writes, the bigger risk is behavior, not latency: retries, idempotency, and transaction boundaries.
If a payment authorization endpoint has a 900 ms upstream timeout and your gateway retries once on 503, you can create duplicate side effects unless the downstream service enforces idempotency keys. The route config is now a business correctness issue.
The routing layer must own observability and rollback
If your dashboard cannot answer "which requests hit monolith versus new service, by tenant and outcome" in one query, your migration is running blind.
The routing layer should stamp every request with migration metadata. At minimum:
- route ID
- destination service
- rollout cohort
- request ID and trace ID
- policy version
- fallback reason, if any
Example: Envoy-style access log fields
{
"timestamp":"2026-08-10T10:15:21.114Z",
"route_id":"checkout-write-v1",
"cluster":"checkout-service",
"tenant_id":"acme-eu",
"cohort":"canary-10",
"trace_id":"4f0d7e5a3b5e1c2d",
"status":200,
"duration_ms":143,
"upstream_service_time_ms":136,
"fallback":false,
"policy_version":"2026-08-01.3"
}
With that structure, you can compare old and new paths quickly. For example:
- Monolith
GET /orders/{id}p95: 280 ms - New query service p95: 118 ms
- Gateway overhead: 6 ms p95
- Error rate difference: +0.08% on the new path
- Cache hit rate improvement: 19 points after moving enterprise tenants
Those are the numbers that let an engineering lead continue the migration with confidence.
Rollback must be operationally boring
A strangler fig migration fails when rollback is slow, manual, or ambiguous. If changing traffic back requires editing app code, redeploying services, or coordinating three teams, you do not have a rollback plan.
Aim for these rollback properties:
- single control point
- no application redeploy
- propagation under 60 seconds globally, under 15 seconds regionally
- clear audit trail of who changed what
- precomputed safe fallback route
A simple GitOps-driven route toggle can work well:
#!/usr/bin/env bash
set -euo pipefail
ROUTE_NAME="checkout-write-v1"
PATCH='{"spec":{"http":[{"name":"checkout-write-v1","route":[{"destination":{"host":"monolith"},"weight":100},{"destination":{"host":"checkout-service"},"weight":0}]}]}}'
kubectl -n edge patch virtualservice checkout-routing --type merge -p "$PATCH"
echo "Rolled back ${ROUTE_NAME} to monolith at $(date -Iseconds)"
This is not glamorous. It is what saves revenue at 2:13 a.m.
Consistency problems usually surface as routing problems first
Teams often say, "We have a data consistency issue," when the first visible symptom is actually a routing inconsistency.
Here is the pattern:
- reads move to the new service
- writes remain on the monolith
- cache invalidation differs by path
- some requests are pinned by session, others by percentage rollout
- customers see different answers depending on route choice
That is not just a data issue. It is a routing policy issue because the system lacks a deterministic rule for read-after-write behavior.
Use explicit consistency modes per route
For each migrated capability, define one of these modes:
- Strong via legacy write path: reads after writes stay on monolith for a bounded window, such as 2 minutes.
- Session-consistent: a user who writes is pinned to the same backend family for the session.
- Eventual with disclosure: acceptable for low-risk views like analytics summaries.
A route policy can encode this:
route: customer-profile-read
match:
path_prefix: /api/customer/profile
policy:
consistency_mode: session_consistent
session_affinity:
cookie: mig_backend
ttl_seconds: 1800
read_after_write_window_seconds: 120
fallback_on_stale_projection: true
This matters most in domains like pricing, inventory, payments, and entitlement checks. In one manufacturing portal migration, inventory reads moved to a new service and looked fine in synthetic tests. In production, 2.4% of users saw stale stock after order updates because mobile traffic bypassed the affinity rule enforced only at the web gateway. The bug was fixed by moving the policy to a shared edge-and-mesh routing contract.
Common Pitfalls
The routing layer fails in predictable ways. The good news is that you can avoid most of them with a few design rules.
1. Splitting routing logic across too many control planes
A CDN rule handles geography, the API gateway handles auth, the mesh handles retries, and the app handles tenant exceptions. Nobody can explain the final decision path.
Avoid it by assigning clear ownership:
- edge: client classification, coarse routing, WAF, auth entry
- gateway: API policy, migration decisions, observability
- mesh: service-to-service resilience and mTLS
- app: business logic only
2. Retrying non-idempotent writes
This is still one of the most expensive migration mistakes. A gateway-level retry on POST /payments can create duplicate charges or duplicate workflow steps.
Avoid it by:
- setting retries to
0for non-idempotent writes - requiring idempotency keys on external write APIs
- logging retry reason and attempt count
3. Ignoring client version skew
Your new service may be correct for web clients and wrong for an old mobile app that still expects a deprecated enum or field ordering. In 2026, many enterprises still support long-lived device fleets and embedded clients.
Avoid it by routing on client capability, not just endpoint path.
4. Measuring service health without route health
A service can be healthy while the migration path is failing. Example: the new service returns 200s, but a header-based route rule misclassifies 12% of enterprise tenants.
Avoid it by creating route-level SLOs:
- route match accuracy
- fallback rate
- p95 latency by cohort
- error budget burn by destination
5. Forgetting compliance and audit requirements
When traffic shifts between systems, your data handling obligations may shift too. This matters for regional residency, retention, and access logging.
Avoid it by attaching policy metadata to routes and exporting route decision logs to your SIEM. For regulated workloads, keep route change approvals in the same audit stream as infrastructure changes.
A practical blueprint for your first 90 days
If you are planning a strangler fig migration now, treat routing as a product with an owner, a roadmap, and acceptance criteria.
Days 1-30: establish the control plane
- Pick the authoritative routing layer for north-south traffic.
- Define route schema: match, destination, policy, observability, rollback.
- Standardize headers for tenant, cohort, trace, and client capability.
- Build dashboards for route-level latency, errors, and fallback.
Days 31-60: prove the first low-risk cutover
- Start with read-heavy, low-regret endpoints.
- Shadow traffic before live cutover.
- Compare response parity on a statistically meaningful sample, such as 50,000 requests.
- Set rollback thresholds, for example error rate delta >0.5% or p95 latency regression >25%.
Days 61-90: harden write-path policy
- Add idempotency enforcement.
- Define consistency mode per route.
- Run game days for rollback, stale reads, and partial regional failures.
- Review route ownership with platform, security, and product engineering.
A mature team can usually move its first read domain in 6-10 weeks and its first write domain in 10-16 weeks if the routing layer is designed early. Without that work, migrations often spend those same 16 weeks arguing over where policy should live.
Key Takeaways
- Treat the routing layer as migration infrastructure, not plumbing.
- Design routes around decisions like tenant, client version, and consistency mode, not just URLs.
- Stamp every request with route metadata so you can prove behavior and roll back fast.
- Keep non-idempotent writes conservative until idempotency and fallback rules are tested.
- Define route-level SLOs and rollback thresholds before the first cutover.
- Give one team clear ownership of routing policy across gateway, edge, and mesh.
This article was written by an AI system and published pending human review. Verify anything you intend to act on.
Written by
Nesqual Tech AI
Nesqual Tech
Have a project in mind?
Get an instant AI price estimate for it, or talk directly to our team.
One email a month on what we learn building with AI