Enterprise Architecture Patterns Tutorial 0/65 lessons ~6 min read Lesson 53

    Strangler Fig

    Strangler Fig incrementally replaces a legacy system by routing traffic slice-by-slice to new implementations while the old system continues running.

    Course progress0%
    Focus
    18 guided sections
    Practice signal
    Examples included
    Career prep
    Interview Q&A included

    Introduction

    Strangler Fig incrementally replaces a legacy system by routing traffic slice-by-slice to new implementations while the old system continues running. Named after the fig vine that grows around a host tree until the host dies, the pattern avoids big-bang rewrites — the riskiest migration strategy at Amazon scale where a single hour of checkout downtime costs tens of millions.

    Real production story

    In 2019, an Amazon retail team owned a 2.1M-line Java monolith powering product detail, cart, and checkout. Leadership demanded microservices for independent deploy, but a 18-month freeze-and-rewrite was rejected after a competitor's failed migration made headlines. The staff architect proposed Strangler Fig: put an API gateway in front of the monolith, extract read-only product catalog first (low risk), then cart mutations, then payment authorization last.

    Within six quarters, 73% of read traffic hit new services. The monolith still handled edge cases — returns, gift cards, marketplace seller overrides — but new features shipped only on the strangler path. Prime Day 2020 ran with zero rollback events. MTTR for catalog bugs dropped from 4 hours to 22 minutes because teams owned bounded services with independent deploy pipelines.

    Business problem

    Business pressure: Amazon's retail platform must ship weekly while maintaining 99.99% availability during Prime Day, Black Friday, and regional flash events. A big-bang rewrite freezes feature delivery for quarters and concentrates risk into a single cutover weekend.

    • Revenue at risk: Checkout downtime during peak events directly impacts GMV — leadership will not approve a migration that pauses revenue-generating features for 12+ months.
    • Engineering velocity: 400+ engineers touch the monolith; merge queues exceed 3 days; every feature risks regression in unrelated domains.
    • Compliance / trust: PCI and marketplace seller SLAs require explainable rollback paths — "we'll cut over Saturday and hope" fails architecture review.

    Architecture overview

    Strangler Fig in production means a facade (API gateway, reverse proxy, or routing layer) sits in front of legacy and new systems. Each migrated capability gets a routing rule: if the request matches slice N, route to the new service; otherwise, fall through to legacy. Over time, the legacy surface shrinks until decommission.

    • Definition: Incremental replacement pattern — new system grows around legacy until legacy can be retired without a coordinated big bang.
    • When to adopt: Large legacy with clear domain seams, high availability requirements, and business pressure to ship during migration.
    • When to defer: Small codebase (<50K LOC), low traffic, or team can afford a clean rewrite with minimal production risk.
    • Operability: Dual-run periods require parity dashboards, shadow traffic comparison, and feature flags per routing slice.

    Architecture motivation

    Why architects care: Strangler Fig addresses evolvability, risk containment, and continuous delivery simultaneously. The naive alternative — freeze the monolith, rewrite in parallel, switch traffic in one weekend — works in slide decks but fails when production edge cases surface on hour three of cutover.

    • Force: Legacy system cannot scale to new business models (marketplace, subscriptions, same-day delivery) without structural change.
    • Constraint: Cannot stop feature development; business units compete for roadmap slots every quarter.
    • Outcome: Incremental traffic migration with measurable parity, per-slice rollback, and ADRs documenting extraction order.

    Internal architecture

    Amazon retail strangler topology — gateway routes by capability, not by team:

    • Router owns the migration map — not individual services. Central visibility into which paths are strangler vs legacy.
    • Extract read before write — catalog reads are idempotent; cart/checkout writes need saga and reconciliation.
    • Legacy remains authoritative for unmigrated domains until parity sign-off per slice.
    text
    Client (web / mobile / Alexa)
    Application Load Balancer
    Strangler Router (Envoy / custom)
    ┌─────────────────────────────────────┐
    │ Route table (feature-flag driven) │
    │ /catalog/* → catalog-svc-v2 │
    │ /cart/items → cart-svc-v2 │
    │ /checkout/* → legacy-monolith │
    │ /returns/* → legacy-monolith │
    └─────────────────────────────────────┘
    ↓ ↓
    New microservices Legacy monolith
    (K8s, own DB) (shared Oracle DB)

    Data flow

    Strangler read path: request hits router → matched to new catalog service → new service reads its own PostgreSQL → response returned. Shadow mode: same request duplicated to legacy asynchronously; parity job compares JSON diff.

    • Write path: dual-write to legacy + new store during transition; reconciliation worker fixes drift nightly.
    • Read path: route 5% canary traffic to new service; promote to 100% when error rate and p99 match legacy within SLO.
    • Async path: domain events from new services backfill legacy read models until legacy decommission.
    typescript
    // Strangler routing middleware — production pattern
    interface RouteSlice {
    pathPrefix: string;
    target: "legacy" | "new";
    canaryPercent: number;
    parityCheck: boolean;
    }
    const SLICES: RouteSlice[] = [
    { pathPrefix: "/v2/catalog", target: "new", canaryPercent: 100, parityCheck: false },
    { pathPrefix: "/cart", target: "new", canaryPercent: 25, parityCheck: true },
    { pathPrefix: "/checkout", target: "legacy", canaryPercent: 0, parityCheck: false },
    ];
    function route(req: Request): "legacy" | "new" {
    const slice = SLICES.find((s) => req.path.startsWith(s.pathPrefix));
    if (!slice || slice.target === "legacy") return "legacy";
    const bucket = hashTenant(req.headers["x-customer-id"]) % 100;
    return bucket < slice.canaryPercent ? "new" : "legacy";
    }

    System design diagram

    Two diagrams show the Strangler Fig topology and the primary request/event path used in production at scale.

    Strangler Fig — system view
    API Gateway
    Edge
    Strangler router
    Core
    New services
    Data
    Legacy monolith
    Async
    High-level topology for Strangler Fig.
    Strangler Fig — request / event flow
    Route request
    Ingress
    Match slice?
    Store
    New or legacy
    Store
    Compare parity
    Emit
    Follow this path when reviewing production designs.

    Production code example

    Reference implementation — strangler router with canary, parity shadow, and metrics wired for Amazon-class operations:

    • Metrics: RED per slice (rate, errors, duration) split by legacy vs new target.
    • Feature flags: canary percent stored in LaunchDarkly / AppConfig — not hardcoded in router.
    • Rollback: set canary to 0% in under 60 seconds without redeploy.
    typescript
    // Express + Envoy-style strangler facade
    import express from "express";
    import { createProxyMiddleware } from "http-proxy-middleware";
    import { metrics } from "./observability";
    const app = express();
    const LEGACY = process.env.LEGACY_URL!;
    const CATALOG_V2 = process.env.CATALOG_V2_URL!;
    app.use("/v2/catalog", (req, res, next) => {
    const useNew = shouldCanary(req, "catalog", 100);
    metrics.increment("strangler.route", { slice: "catalog", target: useNew ? "new" : "legacy" });
    if (useNew) {
    if (process.env.SHADOW_PARITY === "true") shadowCompare(req, LEGACY, CATALOG_V2);
    return createProxyMiddleware({ target: CATALOG_V2, changeOrigin: true })(req, res, next);
    }
    return createProxyMiddleware({ target: LEGACY, changeOrigin: true })(req, res, next);
    });
    app.use(createProxyMiddleware({ target: LEGACY, changeOrigin: true }));
    app.listen(8080);

    Enterprise case study

    Amazon retail — Strangler Fig over 6 quarters: Platform team built a paved-road strangler template: Envoy route config, parity dashboard, and extraction ADR checklist. Product teams adopted via self-service with architecture review for write-path extractions.

    • Before: 2.1M-line monolith; 4-hour MTTR; merge queue 3+ days; one failed big-bang attempt shelved.
    • Decision: Strangler with read-first extraction order; central router team owns route table; domain teams own new services.
    • After: 73% read traffic on new services; Prime Day zero rollbacks; new feature lead time dropped from 6 weeks to 9 days for migrated domains.

    Trade-offs

    • Complexity vs risk: Running dual systems doubles operational surface — buys incremental rollback and continuous feature delivery.
    • Data parity vs speed: Strict dual-write reconciliation slows extraction — relax for read-only slices, tighten for financial writes.
    • Router centralization vs team autonomy: Central route table enables visibility; becomes bottleneck if every team edits it without review.

    Security considerations

    Security is architectural: Strangler migration doubles the auth surface — legacy session tokens, new OAuth scopes, and cross-system identity mapping must be consistent.

    • Identity: Map legacy customer IDs to new service principals; mTLS between router and extracted services — not IP allowlists.
    • Data: PCI scope expands during dual-run if both systems touch card data — minimize card data fan-out; tokenize at gateway.
    • Supply chain: New services get independent CI/CD with signed images; legacy deploy path remains until slice decommission.

    Scalability analysis

    Scale dimensions: Amazon engineers plan for Prime Day traffic (10× baseline), geographic expansion, and 50+ extraction slices running concurrently — not just QPS on one service.

    • Horizontal scale: Router tier is stateless and autoscales; each extracted service scales independently with its own DB shard strategy.
    • Hot spots: Popular ASINs during flash sales dominate catalog reads — new service needs CDN + read replicas before cutover.
    • Cost: Dual-run period runs two compute stacks — budget 6–12 months of 1.3–1.8× infra cost during active migration.

    Failure scenarios

    What breaks: Parity drift between legacy and new catalog causes wrong prices; router misconfiguration sends checkout to unmigrated path; dual-write partial failure leaves inconsistent cart state.

    • Parity mismatch: New catalog returns stale inventory — flash sale oversells. Mitigate with shadow traffic diff alerts before canary promotion.
    • Router misroute: Feature flag typo sends 100% checkout to new incomplete service — use flag audit log and automatic rollback on error-rate spike.
    • Dual-write failure: Legacy write succeeds, new write fails — reconciliation job must detect and repair within SLA (minutes, not days).

    Staff engineer insights

    • Extract the slice with the clearest domain boundary and lowest write complexity first — not the slice your team finds most annoying.
    • Parity dashboards are not optional. If you cannot prove new == legacy within SLO, you are not ready to increase canary percent.
    • Decommission legacy paths aggressively once stable — dual-run becomes permanent if teams lose incentive to finish the migration.

    Interview questions

    Interview Prep

    Practice concise answers, then expand each card for the explanation.

    3 questions
    1AdvancedQuestionHow do you choose the order of slices in a Strangler Fig migration at Amazon scale?+

    Answer

    Rank slices by: (1) domain boundary clarity, (2) read vs write ratio, (3) blast radius if wrong, (4) business value of independent deploy. Extract read-only catalog before checkout writes. Document order in ADR with rejected alternatives.

    Follow-up

    What signals tell you a slice is NOT ready for canary promotion?
    2AdvancedQuestionPrime Day is 8 weeks away. Leadership wants to finish checkout extraction before the event. What do you recommend?+

    Answer

    Decline checkout extraction before Prime Day unless parity has run at production traffic for 4+ weeks with zero drift. Offer accelerated catalog/PDP extraction instead — lower write risk, measurable win. Present SLO burn analysis showing checkout migration risk vs reward.

    Follow-up

    How do you quantify dual-run infra cost to finance stakeholders?
    3AdvancedQuestionDesign parity checking between legacy and new catalog services without doubling user-facing latency.+

    Answer

    Async shadow: return new service response to user; fire-and-forget duplicate to legacy; compare in worker pool with sampling (1–5% traffic or all for pre-prod). Alert on field-level diff rate > threshold. Never block user path on parity check.

    Follow-up

    How do you handle non-deterministic fields like timestamps in diff comparison?

    Architecture review questions

    • Is extraction order documented in ADR with risk ranking per slice?
    • Does each slice have canary rollout plan, rollback procedure, and parity dashboard?
    • Are write-path slices using dual-write + reconciliation, not blind cutover?
    • Is legacy decommission date set per slice to prevent permanent dual-run?
    • Can router change canary percent in <60s without full redeploy?
    • Security: auth mapping between legacy and new verified for each migrated path?

    Summary

    Strangler Fig at enterprise scale means growing new services around legacy until the host can die — without a big-bang weekend. At Amazon, success depends on slice ordering, async parity, canary routing, and aggressive legacy decommission. Make the migration map visible, measurable, and reversible.

    Ready to mark this lesson complete?Track your journey across the entire course.