<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Swetha Golla</title>
    <description>The latest articles on DEV Community by Swetha Golla (@swethagolla).</description>
    <link>https://dev.to/swethagolla</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4024475%2F34f38ac7-e2c1-4751-9661-3abeebe68bcf.png</url>
      <title>DEV Community: Swetha Golla</title>
      <link>https://dev.to/swethagolla</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/swethagolla"/>
    <language>en</language>
    <item>
      <title>REST vs GraphQL vs gRPC in 2026: An Architect's Decision Matrix</title>
      <dc:creator>Swetha Golla</dc:creator>
      <pubDate>Tue, 21 Jul 2026 20:59:58 +0000</pubDate>
      <link>https://dev.to/swethagolla/rest-vs-graphql-vs-grpc-in-2026-an-architects-decision-matrix-f7h</link>
      <guid>https://dev.to/swethagolla/rest-vs-graphql-vs-grpc-in-2026-an-architects-decision-matrix-f7h</guid>
      <description>&lt;p&gt;&lt;em&gt;By &lt;a href="https://www.linkedin.com/in/swethagolla" rel="noopener noreferrer"&gt;Swetha Golla&lt;/a&gt; · ~6 min read · Senior Application Architect&lt;/em&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;🔗 This post has a &lt;strong&gt;live interactive version&lt;/strong&gt; with a clickable decision tool — pick your boundary (public API, internal service-to-service, or mobile/bandwidth-constrained) and see the verdict — plus a round-trip diagram comparing REST, GraphQL, and gRPC: &lt;a href="https://swethagolla-eng.github.io/posts/03-rest-graphql-grpc-compared.html" rel="noopener noreferrer"&gt;read it here&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Public API?&lt;/strong&gt; REST. Cacheable, curl-able, and your consumers already know it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Service-to-service inside your own walls?&lt;/strong&gt; gRPC. Contracts, streaming, and protobuf serialization that's consistently ~3x faster than JSON (deserialization depends on payload size) — the perf and type-safety win. Not for a public API, though: browsers can't open a native gRPC connection, so external consumers would need grpc-web plus a translating proxy — real infrastructure most third parties won't build just to call you.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;One backend feeding many divergent UIs?&lt;/strong&gt; GraphQL — but only if you'll staff the schema like a product.&lt;/li&gt;
&lt;li&gt;Protocol choice is an &lt;strong&gt;organizational&lt;/strong&gt; decision wearing a technical costume. Pick per boundary, not per company.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The Setup
&lt;/h2&gt;

&lt;p&gt;Picture a mid-sized logistics platform with a driver mobile app, a dispatcher web console, 40+ internal services, and a partner API that third-party warehouses integrate against. The team is consolidating three generations of API styles into a deliberate standard, and every faction has a favorite. The wrong call gets baked into every integration for the next five years.&lt;/p&gt;

&lt;p&gt;The mistake this team almost made — the mistake most teams make — is picking &lt;em&gt;one&lt;/em&gt; winner. There isn't one. There are three boundaries, and each has a correct answer.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pick your boundary
&lt;/h2&gt;

&lt;p&gt;The live version lets you click through each context. Here's the same breakdown in prose.&lt;/p&gt;

&lt;h3&gt;
  
  
  Public-facing API → REST
&lt;/h3&gt;

&lt;p&gt;Your partners' junior dev with curl and a Stripe-shaped mental model is your real user. REST wins on &lt;strong&gt;zero onboarding friction&lt;/strong&gt;, HTTP caching that CDNs understand natively (ETags, Cache-Control — free infrastructure), and 20 years of tooling: gateways, rate limiters, OpenAPI codegen, Postman. Versioning is a solved, boring problem.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Watch out:&lt;/em&gt; resist the urge to expose GraphQL publicly "for flexibility." You'd be handing anonymous strangers the ability to write expensive queries against your database. This isn't hypothetical: Shopify and GitHub both run their public GraphQL APIs on query-cost rate limiting instead of simple per-request limits — Shopify prices every query in points calculated from the objects and connections it touches, before execution even starts. That's the tax public GraphQL collects; you've effectively rebuilt REST's simplicity, just with a cost engine instead of a request counter.&lt;/p&gt;

&lt;h3&gt;
  
  
  Internal service-to-service → gRPC
&lt;/h3&gt;

&lt;p&gt;Inside your own walls, you control both ends — so spend nothing on human-readability and everything on &lt;strong&gt;contracts and efficiency&lt;/strong&gt;. Protobuf schemas give you compile-time breakage detection across teams, generated clients in every language, and bidirectional streaming for the workloads REST handles awkwardly (progress updates, event feeds). gRPC's own published mobile benchmarks are more nuanced than the marketing pitch, and more credible for it: protobuf &lt;em&gt;serialization&lt;/em&gt; is consistently about 3x faster than JSON regardless of message size, but &lt;em&gt;deserialization&lt;/em&gt; depends on size — JSON is actually ~1.5x faster than protobuf for small messages under 1KB, while protobuf pulls ahead by about 2x for messages over 15KB. Gzip JSON and protobuf's serialization edge grows past 5x, with deserialization roughly even at small sizes and protobuf ~3x faster at larger ones. HTTP/2 multiplexing and HPACK header compression add further gains, and the gap widens with message size and load.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Watch out:&lt;/em&gt; debuggability drops. You'll want grpcurl, server reflection, and good interceptor-level logging from day one — &lt;em&gt;"I can't just read the payload in the proxy logs"&lt;/em&gt; is the #1 complaint from teams six months in. Also: "internal" means service-to-service. A browser can't open a native gRPC connection — you'd need grpc-web plus a proxy that translates back to HTTP/2, which is real extra infrastructure, not a footnote.&lt;/p&gt;

&lt;h3&gt;
  
  
  Mobile / bandwidth-constrained → GraphQL
&lt;/h3&gt;

&lt;p&gt;This is where GraphQL genuinely earns its keep: a screen needs a user, their shipments, and each shipment's latest status, on a flaky cellular link. In my illustrative repo POC (not a formal benchmark — see below), that's 1 trip and 33% of REST's bytes for one representative shape. On a 300ms-RTT connection, collapsing 5 trips into 1 is over a second of perceived latency back.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Watch out:&lt;/em&gt; the "N+1 problem" doesn't disappear, it moves — off the client's round-trips and onto your resolvers, where naive field resolution re-triggers a query per item unless you batch it (DataLoader et al.). And once anonymous or semi-trusted clients can shape their own queries, a single nested query can fan out into a database-melting number of calls. This is exactly why Shopify and GitHub don't rate-limit their public GraphQL APIs by request count — they charge per query based on the objects and connections it touches, computed before execution. If you're not willing to build (or buy) that same cost-accounting and resolver-batching machinery, don't ship a general-purpose graph. A handful of purpose-built REST "screen endpoints" (BFF pattern) gets you 80% of the win at 20% of the operational cost.&lt;/p&gt;

&lt;h2&gt;
  
  
  The trade-off matrix
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;REST&lt;/th&gt;
&lt;th&gt;GraphQL&lt;/th&gt;
&lt;th&gt;gRPC&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Payload size / efficiency&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Full resources, over-fetching; N+1 trips for nested data&lt;/td&gt;
&lt;td&gt;Exact fields, one trip — still JSON text on the wire&lt;/td&gt;
&lt;td&gt;Binary protobuf — serialization ~3x faster than JSON at any size; deserialization favors JSON (~1.5x) under 1KB but protobuf (~2x) over 15KB; streaming built in&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Tooling / debuggability&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;curl, browser, any proxy — everything speaks it&lt;/td&gt;
&lt;td&gt;Introspection + GraphiQL are excellent; errors hide inside 200 OK&lt;/td&gt;
&lt;td&gt;Opaque binary; needs grpcurl, reflection, protoc toolchain&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Caching support&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;First-class: ETags, CDNs, browser cache — all free&lt;/td&gt;
&lt;td&gt;POST-everything defeats HTTP caching; needs persisted queries + client normalization&lt;/td&gt;
&lt;td&gt;Effectively DIY — but internal calls rarely need shared caches&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Consumer learning curve&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Near zero — the lingua franca of the web&lt;/td&gt;
&lt;td&gt;Real curve: query language, fragments, error semantics, client libs&lt;/td&gt;
&lt;td&gt;Generated clients feel like local calls; proto/build setup is a tax on outsiders&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;em&gt;(In the table above, "wins" and "costs" are relative to each other — every cell is a real trade-off, not a free win.)&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Numbers, not vibes
&lt;/h2&gt;

&lt;p&gt;I built a small stdlib-Python POC (linked below) simulating one concrete request — "fetch a user, their 3 posts, and 2 comments per post" — across all three styles. &lt;strong&gt;This is a simplified illustrative demo, not a formal benchmark:&lt;/strong&gt; one hand-picked payload shape, no compression, no real network, no server-side processing cost modeled. Treat the percentages as "here's the mechanism," not "here's the industry number."&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Protocol   Round-trips   ~Bytes    vs REST
REST                 5    3,105       100%
GraphQL              1    1,017        33%
gRPC                 1      629        20%
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Yes, gzip narrows the byte gap on JSON. It does nothing for the four extra round-trips — and on mobile, round-trips are the whole game.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What gRPC's own benchmarks show:&lt;/strong&gt; gRPC's official "Mobile Benchmarks" post is more nuanced than the usual "binary always wins" pitch — which is exactly why it's worth citing. Protobuf &lt;em&gt;serialization&lt;/em&gt; is consistently about 3x faster than JSON, independent of message size. &lt;em&gt;Deserialization&lt;/em&gt; is size-dependent: JSON is actually ~1.5x faster than protobuf for small messages under 1KB, and only once messages exceed 15KB does protobuf take a clear ~2x deserialization lead. Gzip-compress the JSON and protobuf's serialization edge widens past 5x, while deserialization is roughly a wash at small sizes and swings to protobuf (~3x) at larger ones. Translation: for tiny payloads and small internal RPCs, plain JSON deserialization can be perfectly fine — even faster — and the case for gRPC strengthens as messages and scale grow, which lines up directionally with what the toy POC above shows.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Rule of thumb:&lt;/strong&gt; REST when strangers call you. gRPC when you call yourself. GraphQL when many screens call one graph — and only if you'll staff the schema like a product.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Where I actually stand
&lt;/h2&gt;

&lt;p&gt;Watching these decisions age, the pattern is clear: &lt;strong&gt;most public APIs do not need GraphQL, and most that adopted it are quietly paying a maintenance tax&lt;/strong&gt; — cost limiters, persisted queries, resolver performance archaeology — to solve problems REST never had. Meanwhile gRPC is criminally underused internally: teams keep hand-writing JSON clients between their own services because "REST is simpler," then spend that saved week per quarter debugging contract drift a .proto file would have caught at compile time. The senior move isn't picking the sophisticated option. It's matching the protocol to the trust boundary and refusing to relitigate it every sprint.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://github.com/swethagolla-eng/rest-graphql-grpc-compared" rel="noopener noreferrer"&gt;See the working example →&lt;/a&gt;&lt;/strong&gt; Runnable POC behind the round-trip/byte demo above; published-benchmark figures are cited separately. Disagree with the matrix? Good — tell me which cell and why.&lt;/p&gt;

&lt;h2&gt;
  
  
  Sources &amp;amp; Further Reading
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://grpc.io/blog/mobile-benchmarks/" rel="noopener noreferrer"&gt;gRPC official blog — "Mobile Benchmarks"&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://shopify.engineering/rate-limiting-graphql-apis-calculating-query-complexity" rel="noopener noreferrer"&gt;Shopify Engineering — "Rate Limiting GraphQL APIs by Calculating Query Complexity"&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>api</category>
      <category>webdev</category>
      <category>architecture</category>
      <category>systemdesign</category>
    </item>
    <item>
      <title>Multi-Tenant Isolation: Silo, Pool, or Bridge</title>
      <dc:creator>Swetha Golla</dc:creator>
      <pubDate>Tue, 21 Jul 2026 19:32:35 +0000</pubDate>
      <link>https://dev.to/swethagolla/multi-tenant-isolation-silo-pool-or-bridge-2d07</link>
      <guid>https://dev.to/swethagolla/multi-tenant-isolation-silo-pool-or-bridge-2d07</guid>
      <description>&lt;p&gt;&lt;em&gt;By &lt;a href="https://www.linkedin.com/in/swethagolla" rel="noopener noreferrer"&gt;Swetha Golla&lt;/a&gt; · ~6 min read · Senior Application Architect&lt;/em&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;🔗 This post has a &lt;strong&gt;live interactive version&lt;/strong&gt; with a clickable Silo / Pool / Bridge comparison toggle (isolation strength, cost at ~500 tenants, and blast radius per model) plus a diagram of all three isolation models: &lt;a href="https://swethagolla-eng.github.io/posts/13-multi-tenant-architecture.html" rel="noopener noreferrer"&gt;read it here&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;AWS's SaaS Factory program gave the industry its working vocabulary for this decision: &lt;strong&gt;silo&lt;/strong&gt; (dedicated infra/DB per tenant), &lt;strong&gt;pool&lt;/strong&gt; (shared schema, rows tagged by &lt;code&gt;tenant_id&lt;/code&gt;), and &lt;strong&gt;bridge&lt;/strong&gt; (a deliberate mix).&lt;/li&gt;
&lt;li&gt;Pool is the cheapest model to run at hundreds of tenants, but its isolation is only as strong as your query discipline — a missing &lt;code&gt;WHERE tenant_id = ?&lt;/code&gt; is a real, recurring data-leak bug class, not a hypothetical.&lt;/li&gt;
&lt;li&gt;Silo gives you the cleanest blast-radius and compliance story, but N tenants means N databases to patch, back up, and capacity-plan, and cross-tenant analytics means fanning out across all of them.&lt;/li&gt;
&lt;li&gt;Most production systems, and nearly every regulated one, end up &lt;strong&gt;bridge&lt;/strong&gt; — and the split is usually decided per data type (cardholder data vs. everything else), not once for the whole platform.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The Setup
&lt;/h2&gt;

&lt;p&gt;You're the architect on "PayLoop," a fictional B2B payments platform at 40 tenants and climbing. Onboarding tenant #41 — a mid-size retailer — triggers their security questionnaire, and question 12 is the one that always lands on your desk: &lt;em&gt;"Describe how customer data is logically or physically separated between tenants."&lt;/em&gt; Your answer today is "a &lt;code&gt;tenant_id&lt;/code&gt; column and an ORM scope." That answer is true, it's normal, and it is about to get harder to defend as this platform starts touching settlement data and stored card references for tenants who each expect their compliance boundary to be someone else's problem, never theirs.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Vocabulary the Industry Actually Uses
&lt;/h2&gt;

&lt;p&gt;This isn't a new problem, and you don't have to invent the framing from scratch. AWS's SaaS Factory program formalized it in the &lt;em&gt;SaaS Tenant Isolation Strategies&lt;/em&gt; whitepaper: tenants can be deployed in a fully &lt;strong&gt;silo&lt;/strong&gt; model with dedicated resources, a &lt;strong&gt;pool&lt;/strong&gt; model sharing infrastructure for efficiency and scale, or a &lt;strong&gt;bridge&lt;/strong&gt; model that mixes the two — pooling some parts of the system while siloing others. The AWS Well-Architected SaaS Lens adds the detail that matters most in practice: in a system decomposed into services, isolation is decided per service, sometimes per data store, not once for the whole platform. That's the mental model worth stealing, independent of whether you run on AWS.&lt;/p&gt;

&lt;p&gt;One caveat worth flagging directly: AWS isn't fully consistent with itself here — an older AWS storage whitepaper and the 2020 AWS Database Blog post (both linked below) use "bridge" more narrowly, to mean one shared database with a separate schema per tenant, not the broader "mix of silo and pool" sense used throughout this piece. Don't assume everyone in a room means the same thing by the word.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;(The live version has a diagram of all three models — silo's separate databases, pool's single tagged table, and bridge routing by tenant tier — worth a look if you want the visual before the prose.)&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What Actually Changes Between the Three Models
&lt;/h2&gt;

&lt;p&gt;Isolation strength and operating cost move in opposite directions as you go from silo to pool, and the honest way to reason about it is blast radius, not vibes. In a silo model, a bug in tenant A's query path can only ever touch tenant A's database — there is no shared table for it to reach across. That's a real property, not marketing: it's structural, not policy-enforced. The price is operational: 500 tenants in a silo model is 500 databases to patch, monitor, back up, and capacity-plan, and a report spanning tenants means fanning a query out across all of them and merging results in application code.&lt;/p&gt;

&lt;p&gt;Pool inverts both sides of that trade: one schema, one set of migrations, one connection pool to tune, and it scales to thousands of tenants without your ops headcount scaling with it. But every tenant's rows sit in the same table, so isolation lives entirely in application code (or a database-enforced layer) remembering to filter — and shared compute means one tenant's traffic spike degrades everyone else's latency, the classic noisy-neighbor problem AWS calls out explicitly in the same whitepaper.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Bug Class That Makes Pool a Real Risk, Not a Hypothetical
&lt;/h2&gt;

&lt;p&gt;"We'll just always filter by tenant_id" sounds like a process fix until you look at where these bugs actually live: not in the well-tested primary API path, but in the report someone wrote in an afternoon, the admin tool, or the background job that queries across a join and forgets the filter three tables deep. That shape of bug is functionally an IDOR (insecure direct object reference) — it compiles, it runs, it returns rows, and nothing about the code looks broken until you check whose data came back. It's exactly why AWS's own Database Blog recommends PostgreSQL Row-Level Security as a defense-in-depth backstop for pooled multi-tenant systems: RLS makes the database itself enforce the tenant filter on every query, so a forgotten &lt;code&gt;WHERE&lt;/code&gt; clause fails closed instead of leaking silently.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Trade-Offs, Honestly
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Isolation&lt;/th&gt;
&lt;th&gt;Ops cost at scale&lt;/th&gt;
&lt;th&gt;Cross-tenant analytics&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Silo&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Structural — a bug can't reach another tenant's data.&lt;/td&gt;
&lt;td&gt;Linear in tenant count — N databases to patch/back up/monitor.&lt;/td&gt;
&lt;td&gt;Fan-out query across every tenant DB, merge in app code.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Pool&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Policy-enforced — as strong as your query discipline (or RLS).&lt;/td&gt;
&lt;td&gt;Roughly flat — one schema scales to thousands of tenants.&lt;/td&gt;
&lt;td&gt;A single &lt;code&gt;GROUP BY tenant_id&lt;/code&gt; away.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Bridge&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Tunable — strong where you need it, cheap where you don't.&lt;/td&gt;
&lt;td&gt;Two operating models to run instead of one — real, but bounded.&lt;/td&gt;
&lt;td&gt;Depends which side each tenant landed on.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Where Compliance Actually Forces the Decision
&lt;/h2&gt;

&lt;p&gt;In payments, this stops being an architecture-review debate and starts being a compliance requirement. The PCI Security Standards Council's own scoping and segmentation guidance doesn't mandate a specific architecture, but it's explicit that segmentation is the accepted way to shrink your cardholder data environment (CDE) — and that any connection across that boundary needs to be justified, restricted, and periodically re-validated with penetration testing. That pushes cardholder data toward stronger isolation almost by default, while giving you no such pressure on, say, tenant UI preferences or notification settings.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;This is exactly the bridge pattern AWS describes&lt;/strong&gt; in its own whitepaper: the web/application tier pooled and shared across all tenants, while the tier holding the sensitive data is siloed or partitioned — decided per layer, not once for the whole stack.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The decision is per data type, not per system.&lt;/strong&gt; A payments platform can legitimately pool tenant metadata, configuration, and analytics while siloing (or applying stricter partitioning to) anything that touches cardholder data or settlement records.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The mistake I see most often isn't picking the wrong model — it's picking one model and applying it uniformly, so either the compliance-sensitive data is under-isolated, or the low-risk data is paying full silo tax for no reason.&lt;/p&gt;

&lt;h2&gt;
  
  
  My Take
&lt;/h2&gt;

&lt;p&gt;Pure silo and pure pool are both easy to explain in a design review and both wrong for a platform that lives past its first dozen tenants. Pure silo turns your platform team into database administrators at scale — you will be patching CVEs across hundreds of instances long before you're building product. Pure pool turns every engineer who touches a query into your isolation boundary, forever, and that's a bet I've watched lose in production: the leak is never in the code someone reviewed carefully, it's in the report nobody thought was sensitive. Bridge isn't a compromise you settle for — it's the only model that lets you spend your isolation budget where a leak is actually expensive and skip it where it isn't. Decide per data type, put a database-enforced backstop (RLS or equivalent) under whatever you pool, and revisit the split when a tenant's size or a regulator's requirement changes — because it will.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Rule of thumb:&lt;/strong&gt; Pool by default for cost and velocity, but &lt;em&gt;silo or add a database-enforced backstop&lt;/em&gt; for any data type where a leak would be a regulatory incident rather than an embarrassment — and make that call per data type, not once for the whole platform.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://github.com/swethagolla-eng/multi-tenant-architecture-patterns" rel="noopener noreferrer"&gt;See the working example →&lt;/a&gt;&lt;/strong&gt; A small, stdlib-only demo showing a pool-model query leak side-by-side with the same bug in a silo model, where it structurally can't leak. Disagree with where the line should sit? Tell me which data type you'd have split differently — I'd genuinely like to hear it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Sources &amp;amp; Further Reading
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;AWS Whitepaper&lt;/strong&gt; — &lt;a href="https://docs.aws.amazon.com/whitepapers/latest/saas-tenant-isolation-strategies/introduction.html" rel="noopener noreferrer"&gt;"SaaS Tenant Isolation Strategies: Isolating Resources in a Multi-Tenant Environment"&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;AWS Well-Architected Framework&lt;/strong&gt; — &lt;a href="https://docs.aws.amazon.com/wellarchitected/latest/saas-lens/silo-pool-and-bridge-models.html" rel="noopener noreferrer"&gt;"Silo, Pool, and Bridge Models — SaaS Lens"&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;AWS Whitepaper&lt;/strong&gt; — &lt;a href="https://docs.aws.amazon.com/whitepapers/latest/multi-tenant-saas-storage-strategies/saas-partitioning-models.html" rel="noopener noreferrer"&gt;"SaaS Storage Strategies: Partitioning Models"&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;AWS Database Blog&lt;/strong&gt; — &lt;a href="https://aws.amazon.com/blogs/database/multi-tenant-data-isolation-with-postgresql-row-level-security/" rel="noopener noreferrer"&gt;"Multi-tenant data isolation with PostgreSQL Row Level Security"&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;PCI Security Standards Council&lt;/strong&gt; — &lt;a href="https://www.pcisecuritystandards.org/documents/Guidance-PCI-DSS-Scoping-and-Segmentation_v1.pdf" rel="noopener noreferrer"&gt;"Guidance for PCI DSS Scoping and Network Segmentation"&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>saas</category>
      <category>architecture</category>
      <category>systemdesign</category>
      <category>softwareengineering</category>
    </item>
    <item>
      <title>Choreography vs Orchestration: The Real Trade-off in Event-Driven Systems</title>
      <dc:creator>Swetha Golla</dc:creator>
      <pubDate>Tue, 21 Jul 2026 14:34:02 +0000</pubDate>
      <link>https://dev.to/swethagolla/choreography-vs-orchestration-the-real-trade-off-in-event-driven-systems-1o9k</link>
      <guid>https://dev.to/swethagolla/choreography-vs-orchestration-the-real-trade-off-in-event-driven-systems-1o9k</guid>
      <description>&lt;p&gt;&lt;em&gt;By &lt;a href="https://www.linkedin.com/in/swethagolla" rel="noopener noreferrer"&gt;Swetha Golla&lt;/a&gt; · ~6 min read · Senior Application Architect&lt;/em&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;🔗 This post has a &lt;strong&gt;live interactive version&lt;/strong&gt; with a clickable choreography-vs-orchestration trace comparison and a wiring diagram of the same order flow shown both ways: &lt;a href="https://swethagolla-eng.github.io/posts/07-event-driven-architecture.html" rel="noopener noreferrer"&gt;read it here&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Choreography&lt;/strong&gt;: services publish events and react to what they hear. No central brain, great decoupling, but the workflow only exists as the sum of every service's subscriptions — nobody can point to the code and say "this is the order flow."&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Orchestration&lt;/strong&gt;: one coordinator calls each service and owns the sequence. Easy to trace, easy to test, but that coordinator becomes a dependency every step is coupled to.&lt;/li&gt;
&lt;li&gt;For payments and anything an auditor will ask about, orchestration usually wins — a single, queryable saga state beats stitching together five services' logs by correlation ID.&lt;/li&gt;
&lt;li&gt;For high fan-out, many-teams systems where eventual consistency is genuinely fine, choreography wins — but only if you invest in distributed tracing before you need it, not after.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Two ways to wire the same three services
&lt;/h2&gt;

&lt;p&gt;Take the most common saga in commerce: an order comes in, inventory gets reserved, a card gets charged, shipping gets notified. Chris Richardson, who did more than anyone to formalize the &lt;em&gt;microservices Saga pattern&lt;/em&gt; (the underlying idea traces back to a 1987 database paper by Garcia-Molina and Salem), defines the two coordination styles precisely: choreography is when "each local transaction publishes domain events that trigger local transactions in other services," and orchestration is when "an orchestrator... tells the participants what local transactions to execute." That's the whole distinction — who decides what happens next, and how that decision gets communicated.&lt;/p&gt;

&lt;p&gt;Martin Fowler's note on event-driven systems is worth reading before you pick either one, because it separates a question people conflate: are you using events to &lt;em&gt;notify&lt;/em&gt; another service that something happened (loosely coupled, "figure out what to do with this"), or as a disguised &lt;em&gt;command&lt;/em&gt; ("do this thing, I just don't want to call it a command")? Choreography only stays healthy when every event is a genuine notification. The moment a service publishes &lt;code&gt;PaymentDeclined&lt;/code&gt; expecting exactly one downstream service to release inventory, you've built an orchestration and pretended it's a choreography — with none of orchestration's visibility.&lt;/p&gt;

&lt;h2&gt;
  
  
  Same flow, two traces
&lt;/h2&gt;

&lt;p&gt;This is the same "order placed → inventory reserved → payment charged → shipping notified" flow, wired two ways — and it's exactly what the runnable Python demo in the companion repo prints.&lt;/p&gt;

&lt;h3&gt;
  
  
  Choreography — events, no central caller
&lt;/h3&gt;

&lt;p&gt;Four services, four independent subscriptions. The Order Service never calls Inventory; it just publishes &lt;code&gt;OrderPlaced&lt;/code&gt; and moves on. Whoever's listening reacts.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;EVENT   OrderPlaced          {order_id: ORD-1001, ...}
  -&amp;gt; InventoryService reacts to OrderPlaced
EVENT   InventoryReserved    {order_id: ORD-1001, ...}
  -&amp;gt; PaymentService reacts to InventoryReserved
EVENT   PaymentCharged       {order_id: ORD-1001, ...}
  -&amp;gt; ShippingService reacts to PaymentCharged
EVENT   ShippingNotified     {order_id: ORD-1001, ...}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;⚠ Watch for:&lt;/strong&gt; no line of code anywhere says "the flow is inventory then payment then shipping" — that sequence only exists as the sum of the subscriptions above.&lt;/p&gt;

&lt;h3&gt;
  
  
  Orchestration — one coordinator, direct calls
&lt;/h3&gt;

&lt;p&gt;One function owns the recipe. It calls each service, checks the result, and decides what happens next — including the compensating action when a step fails.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;CALL    Orchestrator.start(order=ORD-1001)
  -&amp;gt; CALL Orchestrator -&amp;gt; InventoryService.reserve()
  &amp;lt;- OK   InventoryService: reserved
  -&amp;gt; CALL Orchestrator -&amp;gt; PaymentService.charge()
  &amp;lt;- OK   PaymentService: charged
  -&amp;gt; CALL Orchestrator -&amp;gt; ShippingService.notify()
  &amp;lt;- OK   ShippingService: notified
STATE   order ORD-1001 = COMPLETE
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;⚠ Watch for:&lt;/strong&gt; every participant now depends on the orchestrator being up and its contract staying stable — that's the coupling you're trading for the readability above.&lt;/p&gt;

&lt;p&gt;Failure handling is where the two styles diverge hardest, and it's the part diagrams tend to hide. In choreography, a compensating action — say, releasing an inventory hold after a payment decline — has to be triggered by yet another event, published by whichever service happens to be listening for the failure. That service now has an implicit dependency on a failure mode it didn't cause, and that dependency lives in a subscription, not in a place a new engineer would think to look. In orchestration, the same compensation is the next line in the same function that made the failing call — the "what do we undo" logic sits right next to the "what did we try" logic, because one person wrote both on purpose.&lt;/p&gt;

&lt;h2&gt;
  
  
  The trade-off that actually matters
&lt;/h2&gt;

&lt;p&gt;The marketing version of this comparison — "choreography is decoupled and scalable, orchestration is simple and traceable" — is true but incomplete. The sharper way to think about it: choreography trades a debugging tax now for an organizational scaling benefit later. Orchestration trades a coupling cost now for an audit trail you get for free.&lt;/p&gt;

&lt;p&gt;In an order-processing system, that second trade usually wins. When a chargeback dispute lands, or a regulator asks "walk me through what happened to this specific transaction," you want one place to look — the orchestrator's saga state — not a promise that your distributed tracing was configured correctly on the day it mattered. AWS's own writeup of this exact problem is blunt about it: choreography "obfuscates the workflow definition... there is no formal statement that describes steps, permitted transitions, and possible failures." That's not a knock on choreography as a pattern — it's a direct statement of what you're giving up, from the vendor whose entire business is selling you the event bus.&lt;/p&gt;

&lt;p&gt;Where choreography genuinely wins is fan-out and team autonomy at scale: one &lt;code&gt;OrderPlaced&lt;/code&gt; event triggering inventory, fraud scoring, loyalty points, and analytics — four teams, four independent deployments, none of them waiting on a shared orchestrator's release calendar. If you have five or fewer services in the saga and one team that owns the whole business outcome, you don't have that problem yet, and orchestration's traceability is close to free. If you have real fan-out across team boundaries, choreography's decoupling is worth the tracing investment — but budget for it as a first-class requirement, not a "we'll add correlation IDs later" line item, because "later" is always after the first multi-hour incident where nobody can reconstruct what happened.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Dimension&lt;/th&gt;
&lt;th&gt;Choreography&lt;/th&gt;
&lt;th&gt;Orchestration&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Where the flow lives&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Nowhere explicitly — it's the sum of every service's event subscriptions.&lt;/td&gt;
&lt;td&gt;One place — the orchestrator's code or state machine definition.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Debugging "why did this fail"&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Cross-service log correlation, usually by a trace/order ID, across N services.&lt;/td&gt;
&lt;td&gt;One saga state to read — the failure step and compensation are adjacent.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Coupling&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Low — services depend on event contracts, not on each other directly.&lt;/td&gt;
&lt;td&gt;Higher — every participant is coupled to the orchestrator's availability and contract.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Best fit&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;High fan-out, many independent teams, eventual consistency is acceptable.&lt;/td&gt;
&lt;td&gt;Few services, one team or tight coordination, auditability matters (compliance, payments, regulated flows).&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Rule of thumb:&lt;/strong&gt; Fewer than ~5 services in the saga, one team accountable for the outcome, or an auditor/regulator will ask "what happened to this transaction" — orchestrate. Real fan-out across independent teams and eventual consistency is genuinely fine — choreograph, but treat correlation IDs and distributed tracing as day-one infrastructure, not a retrofit.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  The part people underestimate
&lt;/h2&gt;

&lt;p&gt;Most teams don't consciously choose choreography — they back into it because an event bus is already there and adding one more subscriber feels free. It is free, right up until someone asks for a sequence diagram of what actually happens when an order is placed, and the honest answer is "check five repos." Orchestration isn't free either — a Step Functions state machine or a Temporal workflow is a real piece of infrastructure with its own on-call rotation. But it's infrastructure you're choosing on purpose, with a name and an owner, instead of a workflow you discover after the fact by reading Kafka topics.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://github.com/swethagolla-eng/event-driven-architecture-patterns" rel="noopener noreferrer"&gt;See the working example →&lt;/a&gt;&lt;/strong&gt; A runnable choreography-vs-orchestration trace comparison. Which pattern have you shipped in production — did the trade-off play out the way the table above suggests? I'd genuinely like to hear where it didn't.&lt;/p&gt;

&lt;h2&gt;
  
  
  Sources &amp;amp; Further Reading
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://microservices.io/patterns/data/saga.html" rel="noopener noreferrer"&gt;Chris Richardson — "Pattern: Saga" (microservices.io)&lt;/a&gt; — the primary source for the choreography vs. orchestration definitions used here.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://martinfowler.com/articles/201701-event-driven.html" rel="noopener noreferrer"&gt;Martin Fowler — "What do you mean by 'Event-Driven'?"&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://aws.amazon.com/blogs/architecture/use-aws-step-functions-to-monitor-services-choreography/" rel="noopener noreferrer"&gt;AWS Architecture Blog — "Use AWS Step Functions to Monitor Services Choreography"&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://aws.amazon.com/event-driven-architecture/" rel="noopener noreferrer"&gt;AWS — "What is an Event-Driven Architecture?"&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>microservices</category>
      <category>systemdesign</category>
      <category>architecture</category>
      <category>softwareengineering</category>
    </item>
    <item>
      <title>The Strangler Fig Pattern in Practice: A Step-by-Step Legacy Migration Playbook</title>
      <dc:creator>Swetha Golla</dc:creator>
      <pubDate>Fri, 10 Jul 2026 20:33:41 +0000</pubDate>
      <link>https://dev.to/swethagolla/the-strangler-fig-pattern-in-practice-a-step-by-step-legacy-migration-playbook-ad2</link>
      <guid>https://dev.to/swethagolla/the-strangler-fig-pattern-in-practice-a-step-by-step-legacy-migration-playbook-ad2</guid>
      <description>&lt;p&gt;&lt;em&gt;By &lt;a href="https://www.linkedin.com/in/swethagolla" rel="noopener noreferrer"&gt;Swetha Golla&lt;/a&gt; · 6 min read · Senior Application Architect&lt;/em&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;🔗 This post has a &lt;strong&gt;live interactive version&lt;/strong&gt; with a clickable stage-by-stage playbook and traffic-shift diagram: &lt;a href="https://swethagolla-eng.github.io/posts/06-strangler-fig-pattern.html" rel="noopener noreferrer"&gt;read it here&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Never big-bang rewrite a system that's still earning money. Strangle it: put a facade in front, migrate slice by slice, shift traffic by percentage.&lt;/li&gt;
&lt;li&gt;The facade/proxy layer is the single most underestimated piece — budget real engineering time for it, not a config file, including a real anti-corruption layer. Without one, the legacy system's mess leaks straight into your clean new system.&lt;/li&gt;
&lt;li&gt;Route &lt;strong&gt;new features&lt;/strong&gt; to the new system first. You get value in weeks, not after an 18-month rewrite.&lt;/li&gt;
&lt;li&gt;Every stage must be independently rollback-able. If you can't flip back in minutes, you've built a slow big-bang, not a strangler.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The Setup
&lt;/h2&gt;

&lt;p&gt;You've inherited a 15-year-old order-management monolith at a fictional logistics firm. It processes 40k orders a day, three of the original engineers are gone, and the test coverage is a rumor. Leadership wants it "modernized" by next year, and the loudest voice in the room is proposing a clean-slate rewrite.&lt;/p&gt;

&lt;p&gt;That rewrite will fail. Not because the team is bad — because the legacy system encodes a decade of business rules nobody has written down, and a rewrite forces you to rediscover all of them at once, in production, on launch day. The strangler fig pattern rediscovers them one slice at a time, with a rollback lever in your hand. Here's the playbook.&lt;/p&gt;

&lt;p&gt;Martin Fowler named this pattern after watching actual strangler fig vines on a trip to Queensland: the vine germinates in the canopy of a host tree, sends roots down around the trunk, and over years grows into its own self-sustaining structure as the original tree dies inside it. Swap "tree" for "monolith" and you have the whole strategy — the new system grows up around the old one, taking over piece by piece, until one day the host isn't load-bearing anymore. It's not a novel idea I'm pitching here; it's a well-worn technique documented in AWS's and Microsoft's own migration guidance, among others, precisely because it keeps working across very different stacks.&lt;/p&gt;

&lt;p&gt;The facade/router controls the traffic split at every stage — same seam, only the dial changes: 0% new → 25% → 75% → 100% new, legacy share shrinking the whole time. (See the &lt;a href="https://swethagolla-eng.github.io/posts/06-strangler-fig-pattern.html" rel="noopener noreferrer"&gt;live diagram&lt;/a&gt; for the full visual.)&lt;/p&gt;

&lt;h2&gt;
  
  
  The Five-Stage Playbook
&lt;/h2&gt;

&lt;p&gt;Every one of these is a shippable milestone with its own risks — treat them as gates, not a Gantt chart.&lt;/p&gt;

&lt;h3&gt;
  
  
  Stage 1 — Put a facade/proxy in front
&lt;/h3&gt;

&lt;p&gt;Insert a routing layer between clients and the monolith. On day one it does nothing but pass 100% of traffic through — that's the point. Deploy it, watch it in production for a week or two, and make sure latency, auth, and error handling are transparent. Everything else in this playbook depends on this seam existing and being trusted.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;⚠ Risk:&lt;/strong&gt; Added latency and a new single point of failure. If the facade goes down, everything goes down — give it redundancy and monitoring before touching routing rules.&lt;/p&gt;

&lt;h3&gt;
  
  
  Stage 2 — Route new features to the new system
&lt;/h3&gt;

&lt;p&gt;The monolith is now frozen for new capabilities. Every net-new endpoint or feature is built in the new stack and routed there by the facade. This proves the new system's deploy pipeline, observability, and data access under real traffic — with zero migration risk, because nothing old has moved yet.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;⚠ Risk:&lt;/strong&gt; New features often need legacy data. Read it via a stable interface (API or replicated view), never by reaching into the legacy database directly — shared writes at this stage create coupling you'll pay for in stage 3.&lt;/p&gt;

&lt;h3&gt;
  
  
  Stage 3 — Migrate slices of old functionality behind the facade
&lt;/h3&gt;

&lt;p&gt;Pick one bounded slice — say, order lookup, not order placement — reimplement it in the new system, and route it through the facade. Start with read-heavy, low-write slices. Run shadow traffic first: send requests to both systems, compare responses, only cut over when they agree. Then repeat, slice by slice.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;⚠ Risk:&lt;/strong&gt; Hidden business rules. The legacy code does things nobody remembers, and each slice will surface a few. Shadow comparison finds them cheaply; a bug report from a customer finds them expensively. Also: slices that share write paths with the monolith need explicit data-ownership decisions.&lt;/p&gt;

&lt;h3&gt;
  
  
  Stage 4 — Increase traffic % to the new system
&lt;/h3&gt;

&lt;p&gt;For each migrated slice, ramp traffic by percentage: 1% → 10% → 25% → 75% → 100%, holding at each step long enough to see real error rates and tail latency. The facade's routing table is your throttle and your emergency brake. This is where the strangler pattern pays its rent — every step is reversible in one config change.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;⚠ Risk:&lt;/strong&gt; Data written by the new system during a partial ramp. If you roll back to legacy, can it see those writes? Decide the source of truth per slice before ramping, or a rollback quietly loses data.&lt;/p&gt;

&lt;h3&gt;
  
  
  Stage 5 — Retire the legacy system
&lt;/h3&gt;

&lt;p&gt;When the facade routes 0% of traffic to the monolith, verify with traffic logs — not tribal knowledge — that nothing hits it. Watch for the ghosts: nightly batch jobs, quarterly reports, that one partner integration. Then archive the code, decommission the infra, and hold the retro. This stage is a deliberate project, not an afterthought.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;⚠ Risk:&lt;/strong&gt; Zombie dependencies. Low-frequency consumers (month-end jobs, audit exports) surface weeks after you think you're done. Keep the legacy system dark-but-bootable for one full business cycle before deleting anything.&lt;/p&gt;

&lt;h2&gt;
  
  
  Strangler Fig vs. Big-Bang Rewrite
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Dimension&lt;/th&gt;
&lt;th&gt;Strangler Fig&lt;/th&gt;
&lt;th&gt;Big-Bang Rewrite&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Risk exposure&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Bounded per slice — a bad migration affects one route, rolled back in minutes.&lt;/td&gt;
&lt;td&gt;Total at cutover — every undiscovered business rule fails on the same day.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Time to first value&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Weeks — first new feature ships on the new stack while the monolith still runs.&lt;/td&gt;
&lt;td&gt;12–24 months — nothing ships until everything ships. Scope grows the whole time.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Team disruption&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Sustained dual-system tax — you run two stacks for the whole migration. But no feature freeze.&lt;/td&gt;
&lt;td&gt;Feature freeze — the business waits, then demands "just one thing" in the legacy system anyway.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Rollback-ability&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Built in — the facade is a routing table; flip a percentage back to legacy.&lt;/td&gt;
&lt;td&gt;Effectively none — rolling back a cutover after data has diverged is a second migration.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Rule of thumb:&lt;/strong&gt; If the legacy system still serves production traffic and you can't recite its business rules from documentation, you don't rewrite it — you strangle it. Big-bang is only for systems small enough to fully re-spec in under a quarter. This isn't a free lunch: you're trading a single catastrophic risk for a longer migration and a facade layer you now have to run — it's a better trade for most legacy systems, not a risk-free one.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  The Part Everyone Underestimates
&lt;/h2&gt;

&lt;p&gt;The facade. Teams treat it as an nginx config and a Friday afternoon. It isn't. It owns authentication passthrough and consistent error semantics — production infrastructure with its own SLA.&lt;/p&gt;

&lt;p&gt;It also owns translation, and that job has a name: an &lt;strong&gt;anti-corruption layer&lt;/strong&gt;, a pattern Eric Evans defined in &lt;em&gt;Domain-Driven Design&lt;/em&gt; for exactly this situation — two systems with incompatible models that still have to talk. Without one, calls pass straight through and the legacy system's quirks — denormalized tables, overloaded status codes, "status=3 has meant something different since 2019" logic — leak directly into your clean new domain model, and months of migration work end up recreating the same debt in new syntax. Worse, that leakage is exactly what makes stage 5 impossible: you can't retire a legacy system your new one has quietly come to depend on.&lt;/p&gt;

&lt;p&gt;When the facade is flimsy, every migration slice becomes a firefight. Staff it like a real component: an owner, tests, observability, a one-line rollback path, and explicit translation logic at the boundary — not an assumption that old and new data shapes match. And be honest about the trade you're making — if the migration stalls (and they do stall, when priorities shift), that facade doesn't disappear. It becomes a permanent extra hop and a second system to maintain. A strangler fig that never finishes is just a new kind of legacy. In my experience, migrations that fail outright don't fail on the new services — they fail because the seam between old and new was treated as an afterthought instead of the thing the whole plan hinges on.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://github.com/swethagolla-eng/strangler-fig-migration-playbook" rel="noopener noreferrer"&gt;See the working example →&lt;/a&gt;&lt;/strong&gt; A runnable facade-router POC that simulates the 0% → 100% traffic shift. Disagree with the playbook? Tell me where — I'd genuinely like to hear which stage broke for you.&lt;/p&gt;

&lt;h2&gt;
  
  
  Sources &amp;amp; Further Reading
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://martinfowler.com/bliki/StranglerFigApplication.html" rel="noopener noreferrer"&gt;Martin Fowler — "Strangler Fig Application"&lt;/a&gt; — the original source of the pattern.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/en-us/azure/architecture/patterns/strangler-fig" rel="noopener noreferrer"&gt;Microsoft Azure Architecture Center — "Strangler Fig pattern"&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/prescriptive-guidance/latest/cloud-design-patterns/strangler-fig.html" rel="noopener noreferrer"&gt;AWS Prescriptive Guidance — "Strangler fig pattern"&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://learn.microsoft.com/en-us/azure/architecture/patterns/anti-corruption-layer" rel="noopener noreferrer"&gt;Microsoft Azure Architecture Center — "Anti-Corruption Layer pattern"&lt;/a&gt; — the translation-layer concept the facade relies on, originally defined by Eric Evans in &lt;em&gt;Domain-Driven Design: Tackling Complexity in the Heart of Software&lt;/em&gt; (2003).&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>softwareengineering</category>
      <category>systemdesign</category>
      <category>architecture</category>
      <category>programming</category>
    </item>
  </channel>
</rss>
