<?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>SNS vs SQS vs Kinesis vs MSK vs EventBridge vs RabbitMQ: An Architect's Decision Matrix</title>
      <dc:creator>Swetha Golla</dc:creator>
      <pubDate>Wed, 05 Aug 2026 20:45:27 +0000</pubDate>
      <link>https://dev.to/swethagolla/sns-vs-sqs-vs-kinesis-vs-msk-vs-eventbridge-vs-rabbitmq-an-architects-decision-matrix-510p</link>
      <guid>https://dev.to/swethagolla/sns-vs-sqs-vs-kinesis-vs-msk-vs-eventbridge-vs-rabbitmq-an-architects-decision-matrix-510p</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; · 8 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 per-service verdict and the full comparison matrix: &lt;a href="https://swethagolla-eng.github.io/posts/14-aws-messaging-streaming-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;Need strict per-key ordering and replay? That's a log, not a queue — Kinesis or MSK. Pick MSK if you need real Kafka wire-protocol compatibility (existing clients, Kafka Streams, ksqlDB, Debezium); pick Kinesis if you'd rather AWS own shard mechanics and you're fine with its API.&lt;/li&gt;
&lt;li&gt;Need routing logic based on event content, not raw throughput? EventBridge — pattern-matching rules to many differently-interested targets, not identical delivery to everyone.&lt;/li&gt;
&lt;li&gt;Need a simple durable buffer between one producer and one consumer group? SQS. Need the same message fanned out to many independent subscribers? SNS — often paired with SQS underneath.&lt;/li&gt;
&lt;li&gt;Already running RabbitMQ, or need AMQP-specific routing? Amazon MQ for RabbitMQ is a lift-and-shift, not a rearchitecture.&lt;/li&gt;
&lt;li&gt;The expensive mistake isn't picking a slightly-suboptimal service — it's picking a queue when you needed a log, or the reverse. That's a rewrite, not a config change.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The setup
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Scope note:&lt;/strong&gt; this is a decision matrix for AWS's own catalog, not a survey of every messaging technology that exists. Self-hosted Kafka, Google Pub/Sub, Azure Service Bus, NATS, Pulsar, and plenty of others solve overlapping problems outside AWS's walls — worth knowing about, out of scope here.&lt;/p&gt;

&lt;p&gt;A platform team is replacing a single overloaded RabbitMQ broker that has become the answer to every "how do services talk to each other" question for three years running: order events, fraud signals, audit trails, third-party webhooks, and a slow-growing analytics pipeline all queue through it. It works, until it doesn't — a queue depth spike during a promotion in 2025 backed up every consumer behind it, including ones that had nothing to do with the promotion. The team's instinct is to "move it all to AWS-native," as one service. That instinct is the mistake. These six AWS services solve different shapes of the same-sounding problem, and the broker that's currently drowning is actually the right tool for at least one of the workloads still queuing through it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Six AWS services, six verdicts
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;SNS — fan-out, not storage.&lt;/strong&gt; One event needs to reach many independent subscribers — SQS queues, Lambda, mobile push, email/SMS, HTTP endpoints — and you don't need to replay it later. SNS is the AWS-native answer for "one event, many uncoupled consumers."&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Watch out:&lt;/em&gt; nothing is stored. If a subscriber's endpoint is down when you publish, that delivery is gone unless you attached a per-subscription dead-letter queue — and a DLQ catches failed deliveries, not a full replay of everything published.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;MSK — you need real Kafka, not "Kafka-like."&lt;/strong&gt; You already have Kafka clients, Kafka Streams jobs, ksqlDB, or Debezium CDC connectors in production, or you need strict per-partition ordering &lt;em&gt;and&lt;/em&gt; full replay via consumer offset reset, at very high sustained throughput, with an ecosystem that assumes the actual Kafka wire protocol. MSK is that, managed — Cisco's Webex team runs 147 TB and roughly 100 billion messages a day across 12 MSK clusters and 66 brokers, a scale that specifically needed Kafka's partition and consumer-group model.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Watch out:&lt;/em&gt; "managed" narrows the operational surface, it doesn't remove it — worth stating plainly since it's not a comparison AWS's own marketing makes. You're still designing topics and partition counts, sizing brokers, planning consumer-group rebalances, and running version upgrades yourself; MSK Serverless narrows this but doesn't eliminate it. Of the six services here, that's more operational surface than any of the serverless options carry, and more than RabbitMQ's single-broker sizing task too — Kafka's partition/rebalancing model is simply a bigger job to own than a broker instance size.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Kinesis — AWS-native replay, without owning Kafka's model.&lt;/strong&gt; You want a partitioned, replayable log — strict ordering per key, the ability to reset a consumer's iterator to the past and reprocess — but you'd rather AWS handle shard mechanics than run brokers yourself. LaunchDarkly's event-ingestion pipeline scaled from about 1 TB to more than 100 TB a day on Kinesis Data Streams, holding 99.99% availability and 99.99999% durability with end-to-end processing under 30 seconds.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Watch out:&lt;/em&gt; each shard is a hard ceiling — 1,000 records or 1 MiB/s in, 2 MiB/s out — so a hot partition key becomes a bottleneck you shard around, not tune away.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;EventBridge — content-based routing is the whole point.&lt;/strong&gt; Different consumers care about different slices of the same event stream — a fraud service wants only high-risk-flagged orders, a warehouse integration wants only orders from one region. EventBridge's pattern matching (up to 5 targets per rule, input transformers) is built for exactly this, across 20+ native AWS targets plus third-party SaaS event sources.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Watch out:&lt;/em&gt; it's a router, not a log. There's no built-in replay — Archive &amp;amp; Replay is a separate, additionally-billed feature that only replays what you explicitly archived. It's also not sized for the sustained multi-GB/s throughput Kinesis or MSK are built for.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;RabbitMQ (Amazon MQ) — lift-and-shift, or you need AMQP specifically.&lt;/strong&gt; You're migrating an existing RabbitMQ deployment and don't want to rewrite producers/consumers written against AMQP client libraries, or you need features SQS doesn't have — priority queues, exchange-based routing, delayed-message plugins. Amazon MQ for RabbitMQ exists so that migration is "point your existing client at a new endpoint," not "rebuild the messaging layer."&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Worth untangling:&lt;/em&gt; a single RabbitMQ &lt;strong&gt;queue&lt;/strong&gt; is point-to-point, same as SQS — once one consumer reads and acks a message, it's gone from that queue, so two consumers on the same queue split the work rather than both seeing everything. Fan-out to multiple independent subscribers happens one level up, at the &lt;strong&gt;exchange&lt;/strong&gt;: a fanout or topic exchange copies the message into several separate queues, each with its own consumer(s). So RabbitMQ-the-broker (exchange + queues) is the closer analog to SNS+SQS combined — not to SQS alone.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Watch out:&lt;/em&gt; there's no serverless tier — you pick a broker instance size, size storage, and plan cluster-mode HA yourself, and there's no native replay once a message is acknowledged.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;SQS — the simplest durable buffer that works.&lt;/strong&gt; A producer and a consumer group shouldn't be coupled in time — the classic decoupling pattern behind most well-built AWS backends. If you don't need replay or strict global ordering, SQS is the least operationally demanding option on this list, and usually the right default before reaching for anything fancier.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Watch out:&lt;/em&gt; it's a queue, not a log — once a message is deleted (or ages out after up to 14 days) it cannot be replayed, and there's no native fan-out to multiple consumer groups (pair with SNS for that).&lt;/p&gt;

&lt;h2&gt;
  
  
  The comparison matrix
&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;SNS&lt;/th&gt;
&lt;th&gt;MSK&lt;/th&gt;
&lt;th&gt;Kinesis&lt;/th&gt;
&lt;th&gt;EventBridge&lt;/th&gt;
&lt;th&gt;RabbitMQ (Amazon MQ)&lt;/th&gt;
&lt;th&gt;SQS&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Model&lt;/td&gt;
&lt;td&gt;Pub/sub fan-out (push)&lt;/td&gt;
&lt;td&gt;Partitioned log, Kafka API (pull)&lt;/td&gt;
&lt;td&gt;Partitioned log, AWS-native (pull)&lt;/td&gt;
&lt;td&gt;Serverless event bus, content routing (push)&lt;/td&gt;
&lt;td&gt;Broker queue, AMQP exchanges&lt;/td&gt;
&lt;td&gt;Point-to-point queue (pull)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Ordering&lt;/td&gt;
&lt;td&gt;None (standard); strict on FIFO topics&lt;/td&gt;
&lt;td&gt;Strict per-partition, by key&lt;/td&gt;
&lt;td&gt;Strict per-shard, by partition key&lt;/td&gt;
&lt;td&gt;None guaranteed&lt;/td&gt;
&lt;td&gt;Per-queue FIFO by default; needs Single Active Consumer for strict order with &amp;gt;1 consumer&lt;/td&gt;
&lt;td&gt;None (standard); strict per message-group on FIFO&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Delivery semantics&lt;/td&gt;
&lt;td&gt;At-least-once; FIFO adds dedup&lt;/td&gt;
&lt;td&gt;At-least-once by default; exactly-once with transactional/idempotent producer&lt;/td&gt;
&lt;td&gt;At-least-once; consumer owns dedup/offsets&lt;/td&gt;
&lt;td&gt;At-least-once&lt;/td&gt;
&lt;td&gt;At-least-once (manual ack + publisher confirms)&lt;/td&gt;
&lt;td&gt;At-least-once (standard); effectively exactly-once on FIFO with dedup ID&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Retention / Replay&lt;/td&gt;
&lt;td&gt;None — DLQ catches failed deliveries, not replay&lt;/td&gt;
&lt;td&gt;Operator-set (commonly 7+ days); full replay via offset reset&lt;/td&gt;
&lt;td&gt;24h default, extendable to 365 days; replay via shard iterator&lt;/td&gt;
&lt;td&gt;None by default; Archive &amp;amp; Replay is a separate paid add-on&lt;/td&gt;
&lt;td&gt;None — gone once acked, no native replay&lt;/td&gt;
&lt;td&gt;Up to 14 days, no replay — deleted on ack/expiry&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Throughput ceiling&lt;/td&gt;
&lt;td&gt;No fixed headline number, raisable soft quota&lt;/td&gt;
&lt;td&gt;Broker/cluster-bound (147 TB/day across 12 clusters at Cisco Webex)&lt;/td&gt;
&lt;td&gt;1,000 records or 1 MiB/s per shard in, 2 MiB/s out&lt;/td&gt;
&lt;td&gt;10,000 PutEvents/sec + 18,750 invocations/sec (us-east-1 default)&lt;/td&gt;
&lt;td&gt;Broker-instance bound (mq.m7g family), no elastic auto-scale&lt;/td&gt;
&lt;td&gt;Standard effectively unbounded; FIFO high-throughput up to 3,000 msg/sec per partition batched&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Routing&lt;/td&gt;
&lt;td&gt;Topic → many subscribers, filter policies&lt;/td&gt;
&lt;td&gt;Possible via Kafka Streams/consumer-relay (read → filter → re-produce) — app code, not a broker-native rule engine&lt;/td&gt;
&lt;td&gt;None — lives in the consumer app&lt;/td&gt;
&lt;td&gt;Rich pattern-based routing, up to 5 targets/rule&lt;/td&gt;
&lt;td&gt;Exchange types (direct/topic/fanout/headers)&lt;/td&gt;
&lt;td&gt;Single queue → competing consumers, no native fan-out&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Operational overhead&lt;/td&gt;
&lt;td&gt;Near-zero, serverless&lt;/td&gt;
&lt;td&gt;High — topic/partition/broker design is yours&lt;/td&gt;
&lt;td&gt;Low-medium — shard planning (or On-Demand)&lt;/td&gt;
&lt;td&gt;Near-zero, serverless&lt;/td&gt;
&lt;td&gt;Medium — broker sizing, HA, upgrades&lt;/td&gt;
&lt;td&gt;Near-zero, serverless&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cost model&lt;/td&gt;
&lt;td&gt;Per-request + per-delivery&lt;/td&gt;
&lt;td&gt;Per-broker-hour + storage, idle or not&lt;/td&gt;
&lt;td&gt;Per-shard-hour or per-GB + PUT charges&lt;/td&gt;
&lt;td&gt;Per-event + per-target invocation&lt;/td&gt;
&lt;td&gt;Per-broker-hour + storage, idle or not&lt;/td&gt;
&lt;td&gt;Per-request, no idle cost&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Ecosystem&lt;/td&gt;
&lt;td&gt;Deep AWS integration&lt;/td&gt;
&lt;td&gt;Full Kafka wire compatibility&lt;/td&gt;
&lt;td&gt;AWS-native (KCL/KPL, Firehose, Flink)&lt;/td&gt;
&lt;td&gt;20+ AWS targets + SaaS sources&lt;/td&gt;
&lt;td&gt;Standard AMQP — broad non-AWS ecosystem&lt;/td&gt;
&lt;td&gt;Deep AWS integration, universal SDK&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Event size limit&lt;/td&gt;
&lt;td&gt;256 KB&lt;/td&gt;
&lt;td&gt;1 MB default, tunable&lt;/td&gt;
&lt;td&gt;1 MiB default, up to 10 MiB (Oct 2025)&lt;/td&gt;
&lt;td&gt;1 MB (raised from 256 KB, Jan 2026)&lt;/td&gt;
&lt;td&gt;128 MB per message&lt;/td&gt;
&lt;td&gt;256 KB standard (2 GB via Extended Client Library)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;One mapping worth keeping in mind while reading the RabbitMQ column: a single RabbitMQ &lt;em&gt;queue&lt;/em&gt; is point-to-point, the same model SQS uses — it's the &lt;em&gt;exchange&lt;/em&gt; sitting in front of RabbitMQ's queues that does fan-out, which makes RabbitMQ-the-broker closer to "SNS + SQS combined" than to SQS alone.&lt;/p&gt;

&lt;h2&gt;
  
  
  Real-world use cases
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Kinesis&lt;/strong&gt; — LaunchDarkly scaled event ingestion from ~1 TB/day to 100+ TB/day, holding 99.99% availability and 99.99999% durability, specifically for per-consumer isolation and replay.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;MSK&lt;/strong&gt; — Cisco Webex runs 147 TB/day (~100B messages/day) across 12 MSK clusters/66 brokers, migrated in ~6 weeks.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;SNS + SQS&lt;/strong&gt; — the standard AWS fan-out pattern: one topic, one queue per interested consumer, so a slow consumer never blocks the others.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;EventBridge&lt;/strong&gt; — a bus fed by AWS services plus 20+ SaaS partner sources, with rules routing by event content rather than raw volume.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Amazon MQ for RabbitMQ&lt;/strong&gt; — positioned specifically for lift-and-shift of an existing RabbitMQ/ActiveMQ deployment without rewriting producers/consumers.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;SQS&lt;/strong&gt; — the default decoupling buffer: a producer drops work, an independently-scaled worker fleet drains it.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Rule of thumb
&lt;/h2&gt;

&lt;p&gt;Ask two questions before anything else: do you need replay, and do you need strict per-key ordering? Both yes → Kinesis or MSK. Neither → SQS (add SNS in front only if you need fan-out) is simpler and cheaper. If the actual problem is "route by content to different targets," that's EventBridge, not a throughput question. If you already run RabbitMQ, migrating it as-is beats rebuilding what wasn't broken.&lt;/p&gt;

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

&lt;p&gt;The team in the setup above didn't need to replace their RabbitMQ broker — they needed to stop routing everything through one instance of it. The audit trail and analytics pipeline belonged on a log (Kinesis) from day one, because someone was always going to ask "replay last Tuesday" eventually. The webhook fan-out belonged on SNS/SQS, because those consumers never needed to see each other's failures. What was actually fine on RabbitMQ — the order-event routing with its exchange-based rules — stayed on RabbitMQ, just on its own dedicated broker instead of sharing one with everything else. The mistake wasn't the broker they picked three years earlier — RabbitMQ was a reasonable choice at the time, and still is for the workload that kept it. The mistake was answering every later "we need messaging for X" request the same way — bolt it onto the existing broker — instead of asking, each time, which of these models actually fits that workload.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://github.com/swethagolla-eng/aws-messaging-streaming-compared" rel="noopener noreferrer"&gt;See the decision-advisor POC →&lt;/a&gt;&lt;/strong&gt; A small CLI that takes your ordering/replay/routing/throughput requirements and walks through which of the six it actually points to. Disagree with a cell in the matrix? Tell me which one 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://aws.amazon.com/blogs/big-data/launchdarklys-journey-from-ingesting-1-tb-to-100-tb-per-day-with-amazon-kinesis-data-streams/" rel="noopener noreferrer"&gt;AWS Big Data Blog — "LaunchDarkly's journey from ingesting 1 TB to 100 TB per day with Amazon Kinesis Data Streams"&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://aws.amazon.com/solutions/case-studies/cisco-msk-case-study/" rel="noopener noreferrer"&gt;AWS Case Study — Cisco Webex on Amazon MSK&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/AWSSimpleQueueService/latest/SQSDeveloperGuide/high-throughput-fifo.html" rel="noopener noreferrer"&gt;AWS Docs — High throughput for FIFO queues in Amazon SQS&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/eventbridge/latest/userguide/eb-quota.html" rel="noopener noreferrer"&gt;AWS Docs — Amazon EventBridge quotas&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://aws.amazon.com/about-aws/whats-new/2026/01/amazon-eventbridge-increases-event-payload-size-256-kb-1-mb/" rel="noopener noreferrer"&gt;AWS What's New — EventBridge payload size increased to 1 MB (Jan 2026)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://aws.amazon.com/about-aws/whats-new/2025/10/amazon-kinesis-data-streams-10x-larger-record-sizes/" rel="noopener noreferrer"&gt;AWS What's New — Kinesis Data Streams 10x larger record sizes (Oct 2025)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/amazon-mq/latest/developer-guide/amazon-mq-rabbitmq-limits.html" rel="noopener noreferrer"&gt;AWS Docs — Quotas in Amazon MQ for RabbitMQ&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/msk/latest/developerguide/limits.html" rel="noopener noreferrer"&gt;AWS Docs — Amazon MSK quotas&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/streams/latest/dev/kinesis-extended-retention.html" rel="noopener noreferrer"&gt;AWS Docs — Kinesis Data Streams extended data retention&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://rabbitmq.com/docs/queues" rel="noopener noreferrer"&gt;RabbitMQ documentation — Queues (ordering, Single Active Consumer)&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>aws</category>
      <category>architecture</category>
      <category>systemdesign</category>
      <category>kafka</category>
    </item>
    <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>
