Strategic Enterprise Modernization: From Legacy to Cloud-Native AI

Modernizing legacy monoliths with the Strangler Fig pattern: edge routing with Istio, canary and cohort releases, data decoupling, and when not to strangle.


The Problem

Most large organisations run their core business on systems that are 15–30 years old: a monolith, often with a single shared database, a release calendar measured in months, and a shrinking pool of people who understand it. These systems usually work. The problem is that every new capability, whether a mobile channel, a partner API or an AI-assisted workflow, has to be threaded through them, and the cost and risk of change keeps rising.

The tempting answer is a full rewrite. As Martin Fowler and others have documented over two decades of practice, big-bang replacements of critical systems are where modernisation programmes most often stall. The legacy system keeps changing while the rewrite chases it. The old system’s real behaviour (including its bugs, which other systems now depend on) is rarely written down. And no business value ships until the final cutover, which is also the moment of maximum risk.

The practical question is how to replace a running system incrementally, so that each step delivers value and can be rolled back.

The Architectural Solution

The Strangler Fig Application pattern, named by Martin Fowler, grows a new system around the edges of the old one until the old one can be retired. It has three parts.

1. An interception layer

Put a Layer 7 routing point (an API gateway, ingress gateway or service mesh) in front of the monolith before you build anything new. On day one it routes 100% of traffic to the legacy system. This step is low-risk and gives you the control point you’ll use for every later move.

2. Carve out capabilities by business seam

Choose domains with clear boundaries, frequent change and a measurable business case. Domain-driven design’s bounded contexts are the usual tool for finding them. Good first candidates are often new capabilities (such as AI recommendations) or read-heavy domains, because they need little or no data written back to the monolith. Avoid starting with the most entangled core domain, however tempting the business case.

3. Shift traffic progressively

For each capability, move traffic in stages:

  • Shadow / parallel run: send a copy of production traffic to the new service and compare outputs without serving them. This is useful for calculation-heavy domains such as pricing and tax.
  • Cohort routing: route internal users or an opt-in beta group by header or cookie.
  • Weighted canary: route a small percentage of everyone else, and widen it as error-rate and latency SLOs hold.
  • Full cutover: remove the legacy route and delete the dead code in the monolith. If you skip the deletion, you are running two systems indefinitely rather than strangling one.

Patterns of Legacy Displacement (Cartwright, Horn and Lewis) catalogues the supporting techniques, such as Event Interception, Legacy Mimic and Transitional Architecture. Teams tend to skip them, and they are what make the strangler pattern work in practice.

Architecture Diagram

Strangler Fig routing: an API gateway sends 90% of traffic to the legacy monolith and 10% to a new cloud microservice, while a Debezium CDC stream replicates the legacy database into the cloud database

Core Implementation

The routing layer is where the strangler pattern happens day to day. Below is an Istio VirtualService (current networking.istio.io/v1 API) bound to an ingress gateway. Rules are evaluated top to bottom, first match wins, so the most specific rules come first and the legacy catch-all comes last.

apiVersion: networking.istio.io/v1
kind: VirtualService
metadata:
  name: edge-api-router
  namespace: edge
spec:
  hosts:
    - "api.example.com"
  gateways:
    - edge/public-gateway          # without this, rules only apply to in-mesh traffic
  http:
    # 1. Fully migrated capability: always served by the new service.
    - name: recommendations-modern
      match:
        - uri:
            prefix: /v2/recommendations
      route:
        - destination:
            host: recommendations.modern.svc.cluster.local
            port:
              number: 8080
      timeout: 2s

    # 2. Cohort routing: beta users are pinned to the new checkout on every request.
    - name: checkout-beta-cohort
      match:
        - uri:
            prefix: /v1/checkout
          headers:
            x-release-cohort:
              exact: beta
      route:
        - destination:
            host: checkout.modern.svc.cluster.local
            port:
              number: 8080

    # 3. Weighted canary for everyone else (weights must sum to 100).
    - name: checkout-canary
      match:
        - uri:
            prefix: /v1/checkout
      route:
        - destination:
            host: checkout.modern.svc.cluster.local
            port:
              number: 8080
          weight: 5
        - destination:
            host: legacy-monolith.legacy.svc.cluster.local
            port:
              number: 80
          weight: 95
      retries:
        attempts: 0                # checkout is not idempotent; never retry blindly

    # 4. Strangler base: everything not yet migrated stays on the monolith.
    - name: legacy-default
      route:
        - destination:
            host: legacy-monolith.legacy.svc.cluster.local
            port:
              number: 80

Points practitioners get caught by:

  • Weighted routing is per request, not per user. A 5% canary means a single user’s checkout flow can bounce between legacy and modern implementations. For anything stateful, assign cohorts at the edge (a cookie or header set once per user) and route on that, as in rule 2.
  • Retries. Istio retries failed HTTP requests twice by default. That’s fine for idempotent reads and dangerous for payments, so either add idempotency keys or disable retries on those routes.
  • A legacy system outside the cluster needs a ServiceEntry (or a Kubernetes ExternalName service) so the mesh can route to it.
  • Portability. If you’d rather not depend on Istio-specific CRDs, the Kubernetes Gateway API supports weighted traffic splitting through HTTPRoute, and Istio, Envoy Gateway and several other implementations support it. Kong Gateway (NGINX/OpenResty-based) and cloud API gateways offer equivalent features with different configuration.

Manage this file through Git and promote changes per environment (see GitOps-driven infrastructure). Rolling back a canary should be a revert, not a console edit at 2 a.m.

Edge Cases & Limitations

  1. The shared database is the real monolith. If the new service and the legacy system both write to the same tables, you have kept the coupling, added a network hop and risked conflicting writes. Give each extracted capability ownership of its data. While both systems need it, replicate one way with change data capture (CDC) from the system of record, as covered in zero-downtime migration patterns. Avoid ad-hoc dual writes from application code, which can’t be made atomic across two databases.
  2. Lost transactions. A single ACID transaction spanning orders, inventory and billing in the monolith becomes a distributed workflow once those are separate services. The usual replacement is a Saga with compensating actions, which pushes consistency rules into application code and needs careful design for partial failure. Sometimes the right answer is to keep tightly coupled domains together a while longer.
  3. Session and identity continuity. A user moving between legacy and modern pages needs consistent authentication. Stateless tokens (OIDC/JWT) validated at the gateway are one option. A shared session store or a gateway-side token exchange also works and doesn’t require changing the legacy authentication model first.
  4. The long middle. Strangling a large system takes years. Budget for running two platforms, two on-call rotations and duplicate observability for that whole period, and track the percentage of traffic and code retired as a first-class programme metric.
  5. When not to strangle: when the system is small enough to replace in one well-tested release, when the vendor product being replaced can’t be intercepted at a protocol boundary (some batch or mainframe-to-mainframe integrations), or when retiring the capability entirely (the “Retire” option in AWS’s 7 Rs) is cheaper than modernising it.

Key Strategic Takeaways

  • Intercept first, migrate second. Putting a routing layer in front of the monolith is the cheapest, safest first step and makes every later move reversible.
  • Slice by business capability, and take the data with it. Moving code without moving data ownership produces a distributed monolith.
  • Route by cohort, then by weight. Pin users consistently, keep canaries small, and gate promotion on SLOs rather than calendar dates.
  • Delete as you go. A strangler programme succeeds when legacy code and infrastructure are actually decommissioned, not just bypassed.
  • Modernisation enables AI. Clean, well-owned APIs and event streams are what make AI-assisted workflows safe to build later. They are a prerequisite, not a side effect.

  1. Martin Fowler: Strangler Fig Application. The essay that named the pattern.
  2. Ian Cartwright, Rob Horn & James Lewis: Patterns of Legacy Displacement. Detailed supporting patterns for incremental replacement.
  3. Sam Newman: Monolith to Microservices. Migration patterns including branch by abstraction, parallel run and splitting the database.
  4. AWS Prescriptive Guidance: Decomposing monoliths into microservices. Decomposition strategies by business capability and subdomain.
  5. Microsoft Azure Architecture Center: Strangler Fig pattern. Implementation considerations and when not to use the pattern.
  6. Istio Documentation: Traffic Shifting. Weighted routing between service versions with VirtualService and HTTPRoute.
  7. Chris Richardson, microservices.io: Pattern: Saga. Maintaining data consistency across services without distributed transactions.

Disclaimer & Licensing: The architectural patterns, code snippets, and strategies discussed in this publication represent personal research and theoretical paradigms. They are provided "as is" without warranty of any kind. Readers must independently verify and rigorously test any implementation within their specific environments. The author assumes no liability for operational disruptions resulting from the application of these concepts.

This work is licensed under a Creative Commons Attribution-NonCommercial 4.0 International License.