<?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: Gouranga Das Samrat</title>
    <description>The latest articles on DEV Community by Gouranga Das Samrat (@gouranga-das-khulna).</description>
    <link>https://dev.to/gouranga-das-khulna</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%2F2193879%2F834a1499-2027-4355-87be-bb678e90ae5c.jpg</url>
      <title>DEV Community: Gouranga Das Samrat</title>
      <link>https://dev.to/gouranga-das-khulna</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/gouranga-das-khulna"/>
    <language>en</language>
    <item>
      <title>Illegal Instruction: How I Woke Up a Year-Old 'Not Planned' Bug and (Almost) Fixed Vite on Android</title>
      <dc:creator>Gouranga Das Samrat</dc:creator>
      <pubDate>Fri, 07 Aug 2026 09:34:42 +0000</pubDate>
      <link>https://dev.to/gouranga-das-khulna/illegal-instruction-how-i-woke-up-a-year-old-not-planned-bug-and-almost-fixed-vite-on-android-3mba</link>
      <guid>https://dev.to/gouranga-das-khulna/illegal-instruction-how-i-woke-up-a-year-old-not-planned-bug-and-almost-fixed-vite-on-android-3mba</guid>
      <description>&lt;p&gt;My dev server didn't start. It just died.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;pnpm create vite test-react-app &lt;span class="nt"&gt;--template&lt;/span&gt; react-compiler-ts
&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;cd &lt;/span&gt;test-react-app &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; pnpm run dev
&lt;span class="go"&gt;Illegal instruction        pnpm run dev
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No stack trace a human can read. No hint about what to fix. Just &lt;code&gt;Illegal instruction&lt;/code&gt;, and the terminal handing control back like nothing happened. Same story for &lt;code&gt;create-vue&lt;/code&gt;. Same story for a plain &lt;code&gt;vite dev&lt;/code&gt;. Three different starter templates, one identical crash.&lt;/p&gt;

&lt;p&gt;This was on a Termux install on an Android tablet, not some exotic setup. And that detail matters more than it sounds like it should, because a lot of people's &lt;em&gt;only&lt;/em&gt; computer is the phone in their pocket. I'm one of them some days. If you're learning to code from Bangladesh, Nigeria, Indonesia, or a dozen other places, a laptop isn't a given — Termux on an old Android phone is often the whole dev environment. When the standard &lt;code&gt;npm create vite&lt;/code&gt; path doesn't work there, it's not a minor inconvenience. It's a wall.&lt;/p&gt;

&lt;h2&gt;
  
  
  Chasing the actual cause
&lt;/h2&gt;

&lt;p&gt;I started where anyone would: assume it's a Termux problem. Filed &lt;a href="https://github.com/termux/termux-packages/issues/30841" rel="noopener noreferrer"&gt;termux/termux-packages#30841&lt;/a&gt; with logs for Next.js, Vite+React, and Vue, all crashing or silently falling back to a slower path.&lt;/p&gt;

&lt;p&gt;Termux maintainer &lt;code&gt;robertkirkman&lt;/code&gt; pointed out that &lt;code&gt;pkg install turbopack&lt;/code&gt; fixes the Next.js case, since Turbopack is packaged for Termux directly. Tried it — true, but only for Next.js. Vite and Vue don't touch Turbopack at all, and they were still crashing exactly the same way.&lt;/p&gt;

&lt;p&gt;That's when it became clear this wasn't a packaging gap. Vite 6 and up ships &lt;strong&gt;Rolldown&lt;/strong&gt; as its default bundler, a Rust-based, N-API bundler that loads a native binary — &lt;code&gt;@rolldown/binding-android-arm64&lt;/code&gt; for our case. Something in that binary was blowing up the moment it loaded.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;truboxl&lt;/code&gt;, another Termux maintainer, asked for a logcat tombstone instead of just terminal output, and that's where the real evidence showed up:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;pid: 32487, tid: 32487, name: MainThread  &amp;gt;&amp;gt;&amp;gt; node &amp;lt;&amp;lt;&amp;lt;
ABI: 'arm64'
signal 4 (SIGILL), code 1 (ILL_ILLOPC), fault addr 0x6eb3132d64 (*pc=0xc8e9fe93)

backtrace:
  #00 pc 00000000006e2d64  .../rolldown-binding.android-arm64.node
  #01 pc 00000000006e2474  .../rolldown-binding.android-arm64.node
  #02 pc 000000000004a1d8  linker64 (call_array+288)
  #03 pc 000000000004a3d4  linker64 (soinfo::call_constructors+380)
  #04 pc 0000000000035a9c  linker64 (do_dlopen+2076)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's a crash happening during &lt;code&gt;dlopen&lt;/code&gt;, inside the native &lt;code&gt;.node&lt;/code&gt; binary itself, before a single line of JS runs. The classic fingerprint of a native binary compiled with instruction-set assumptions that don't hold on the chip actually running it. Not a Termux bug at all. &lt;code&gt;truboxl&lt;/code&gt; confirmed it and labeled it an upstream issue.&lt;/p&gt;

&lt;h2&gt;
  
  
  The ghost in the closet
&lt;/h2&gt;

&lt;p&gt;Naturally, the next move was to go report this to Rolldown. Except somebody already had.&lt;/p&gt;

&lt;p&gt;Buried in the Rolldown repo was &lt;a href="https://github.com/rolldown/rolldown/issues/6342" rel="noopener noreferrer"&gt;rolldown/rolldown#6342&lt;/a&gt; — "Illegal instruction on android arm64" — opened back in September 2025 by a user named &lt;code&gt;hyperz111&lt;/code&gt;. Same crash signature, same platform. He'd shared a screen recording and full CPU info because he couldn't put together a minimal reproduction repo. The auto-reply bot wanted a reproduction within 14 days or the issue would close. Nobody circled back in time, and it closed as &lt;strong&gt;not planned&lt;/strong&gt; in October 2025.&lt;/p&gt;

&lt;p&gt;Ten months of silence followed. Nearly a year of Vite quietly not working on Android, and basically nobody talking about it.&lt;/p&gt;

&lt;p&gt;Here's the part that bugged me most: "not planned" reads like a policy decision — &lt;em&gt;we've decided Android isn't a target we support&lt;/em&gt;. But that's not actually what was true. Rolldown was already building and publishing &lt;code&gt;@rolldown/binding-android-arm64&lt;/code&gt; on every release. The platform wasn't unsupported in theory; it was broken in practice, and a bot closed the thread because a repro checkbox never got ticked. A build bug got mistaken for a feature request nobody asked for, and then everyone — including me, at first — treated "closed as not planned" as a dead end instead of what it actually was: an unfinished conversation.&lt;/p&gt;

&lt;p&gt;In the meantime, I filed &lt;a href="https://github.com/termux/termux-packages/issues/30852" rel="noopener noreferrer"&gt;termux/termux-packages#30852&lt;/a&gt; asking Termux to package Rolldown directly, the same way it already packages Turbopack, since going through upstream looked like it was going nowhere.&lt;/p&gt;

&lt;h2&gt;
  
  
  Going back with receipts
&lt;/h2&gt;

&lt;p&gt;But a package request downstream doesn't fix the actual bug upstream. So &lt;code&gt;robertkirkman&lt;/code&gt; went back to the closed Rolldown issue with the tombstone trace in hand and reframed the ask. This wasn't "please make Android a fully maintained target." It was narrower: the android-arm64 build already exists and already ships, it's crashing at load on real silicon, and that's a cross-compilation flag problem, not a support-commitment problem.&lt;/p&gt;

&lt;p&gt;That reframing is what actually moved it. Rolldown maintainer &lt;code&gt;shulaoda&lt;/code&gt; reopened #6342 and was refreshingly honest about the gap: the team doesn't have a shelf of random Android devices to test against, so verification from someone with the actual crashing hardware was exactly what they needed.&lt;/p&gt;

&lt;p&gt;Then &lt;code&gt;sapphi-red&lt;/code&gt; spotted the likely root cause, and it's a satisfying one: Rolldown depends on &lt;code&gt;mimalloc&lt;/code&gt;, and its &lt;code&gt;no_opt_arch&lt;/code&gt; feature — the thing that stops mimalloc from assuming newer ARM instructions are present — was only being enabled when &lt;code&gt;target_os = "linux"&lt;/code&gt;. Android runs on Linux under the hood but reports a different &lt;code&gt;target_os&lt;/code&gt;, so the Android build skipped that safety flag entirely and got compiled assuming ARMv8.1 atomic instructions that a chip like a Cortex-A53 simply doesn't have. Since mimalloc initializes inside a library constructor, that lines up exactly with a &lt;code&gt;SIGILL&lt;/code&gt; at &lt;code&gt;dlopen&lt;/code&gt;, before any of Rolldown's own code even runs.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;shulaoda&lt;/code&gt; shipped a preview build in &lt;a href="https://github.com/rolldown/rolldown/pull/10638" rel="noopener noreferrer"&gt;rolldown/rolldown#10638&lt;/a&gt; within the same day and asked for someone with the affected hardware to verify it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Testing it on the actual crashing device
&lt;/h2&gt;

&lt;p&gt;So I did. Cortex-A53, MediaTek MT6762V, the same tablet that had been throwing &lt;code&gt;Illegal instruction&lt;/code&gt; this whole time.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;npx rolldown@1.2.3 &lt;span class="nt"&gt;--version&lt;/span&gt;
&lt;span class="go"&gt;Illegal instruction

&lt;/span&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;pnpm i https://pkg.pr.new/rolldown@10638
&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;npx rolldown &lt;span class="nt"&gt;--version&lt;/span&gt;
&lt;span class="go"&gt;rolldown v1.2.3+commit.a7ba7ad
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No crash. Then the real test: a fresh Vite + React project.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;pnpm create vite test-react-app &lt;span class="nt"&gt;--template&lt;/span&gt; react-compiler-ts
&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;pnpm run dev
&lt;span class="go"&gt;  VITE v6.x.x  ready in 412 ms
  ➜  Local:   http://localhost:5173/
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It just worked. Ten months of &lt;code&gt;Illegal instruction&lt;/code&gt;, gone with one removed &lt;code&gt;target_os&lt;/code&gt; condition.&lt;/p&gt;

&lt;h2&gt;
  
  
  Then I tweeted it
&lt;/h2&gt;

&lt;p&gt;I posted the whole trail on X, tagging Evan You, the Rolldown team, and Vite. Rolldown's &lt;code&gt;shulaoda&lt;/code&gt; — Jerry Zhao — replied publicly the next day, confirmed the team was taking it seriously, and pointed back at the reopened issue and the preview build. I replied that I was already testing it on the affected device and would post results on the issue. Small exchange, but it's the kind of public back-and-forth that keeps an issue from quietly dying a second time.&lt;/p&gt;

&lt;h2&gt;
  
  
  The side quest nobody asked for
&lt;/h2&gt;

&lt;p&gt;While all this was happening upstream, the Termux side didn't just sit around waiting for a release. Contributor &lt;code&gt;DevGitPit&lt;/code&gt; opened &lt;a href="https://github.com/termux/termux-packages/pull/30887" rel="noopener noreferrer"&gt;termux/termux-packages#30887&lt;/a&gt; to package Rolldown 1.2.3 downstream anyway, so Termux users wouldn't be stuck waiting on Rolldown's release cycle. That PR turned into its own small collaboration — &lt;code&gt;robertkirkman&lt;/code&gt; debugging build errors alongside &lt;code&gt;DevGitPit&lt;/code&gt;, and at one point asking the Rolldown team directly whether they'd consider supporting more Android ABIs (32-bit ARM, x86, x86_64) beyond just arm64, since some Termux users are on hardware Rolldown doesn't build for at all.&lt;/p&gt;

&lt;p&gt;Two separate open-source projects, two separate maintainer teams, ended up helping each other out over the same crash. Termux gave Rolldown a real device to test against and a trace nobody on the Rolldown side could have reproduced. Rolldown gave Termux users a real fix instead of a permanent downstream patch to maintain forever.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where it stands right now
&lt;/h2&gt;

&lt;p&gt;The fix works. I've confirmed it on the exact hardware that was crashing. It hasn't landed in a release yet — the preview build is verified, the PR is open, and it's waiting on the Rolldown team to merge and cut a new version. Once that happens, &lt;code&gt;npm install -g rolldown&lt;/code&gt; (or just running &lt;code&gt;create vite&lt;/code&gt;) should work on Android arm64 without anyone needing a Termux-specific package at all.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why I bothered writing this up
&lt;/h2&gt;

&lt;p&gt;"Closed as not planned" isn't always a wall. Sometimes it's a bot doing its job on an issue that never got the follow-up it needed. The difference between a bug staying closed for another year and getting fixed in a week wasn't cleverness — it was a device to reproduce it on, a tombstone instead of a vague description, and enough persistence to go back and ask for the closed issue to be looked at differently instead of filing yet another duplicate.&lt;/p&gt;

&lt;p&gt;If you're running Termux on Android arm64 and hit this, or you're on different silicon — an older Snapdragon, an Exynos, anything that isn't a Cortex-A53 — go pull the fix from &lt;a href="https://github.com/rolldown/rolldown/pull/10638" rel="noopener noreferrer"&gt;#10638&lt;/a&gt; and drop a comment on &lt;a href="https://github.com/rolldown/rolldown/issues/6342" rel="noopener noreferrer"&gt;#6342&lt;/a&gt; with what you find. More devices means more confidence before it ships, and right now that's the only thing standing between this bug and actually being closed for good.&lt;/p&gt;

</description>
      <category>android</category>
      <category>webdev</category>
      <category>opensource</category>
      <category>termux</category>
    </item>
    <item>
      <title>API Versioning</title>
      <dc:creator>Gouranga Das Samrat</dc:creator>
      <pubDate>Sun, 02 Aug 2026 02:00:00 +0000</pubDate>
      <link>https://dev.to/gouranga-das-khulna/api-versioning-2h8m</link>
      <guid>https://dev.to/gouranga-das-khulna/api-versioning-2h8m</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;One-liner:&lt;/strong&gt; API versioning lets you evolve your API without breaking existing clients — you maintain multiple contract versions simultaneously until clients migrate.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  ❓ Why Version Your API?
&lt;/h2&gt;

&lt;p&gt;Once a public API has consumers, you can't change it freely:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Renaming a field breaks every client using the old name&lt;/li&gt;
&lt;li&gt;Removing an endpoint causes 404s for clients that depend on it&lt;/li&gt;
&lt;li&gt;Changing response format silently corrupts client logic&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Versioning gives clients time to migrate at their own pace.&lt;/p&gt;




&lt;h2&gt;
  
  
  🏗️ 4 Versioning Strategies
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. URI Path Versioning ✅ (Most Common)
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="err"&gt;GET /api/v1/users/42
GET /api/v2/users/42   ← new version with different response shape
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Pros:&lt;/strong&gt; Obvious, easy to route, cacheable by CDN&lt;br&gt;&lt;br&gt;
&lt;strong&gt;Cons:&lt;/strong&gt; Pollutes URLs, clients must change URLs to upgrade&lt;/p&gt;


&lt;h3&gt;
  
  
  2. Query Parameter Versioning
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="err"&gt;GET /api/users/42?version=1
GET /api/users/42?version=2
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;&lt;strong&gt;Pros:&lt;/strong&gt; URL stays stable&lt;br&gt;&lt;br&gt;
&lt;strong&gt;Cons:&lt;/strong&gt; Easy to forget, doesn't work well with caching&lt;/p&gt;


&lt;h3&gt;
  
  
  3. Header Versioning
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="err"&gt;GET /api/users/42
Accept: application/vnd.myapi.v2+json
              OR
X-API-Version: 2
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;&lt;strong&gt;Pros:&lt;/strong&gt; Clean URLs, aligns with REST purists&lt;br&gt;&lt;br&gt;
&lt;strong&gt;Cons:&lt;/strong&gt; Not visible in browser, harder to test/share URLs&lt;/p&gt;


&lt;h3&gt;
  
  
  4. Content Negotiation (Accept header)
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="err"&gt;GET /api/users/42
Accept: application/vnd.company.user-v2+json
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Used by GitHub API and Stripe. Powerful but complex.&lt;/p&gt;


&lt;h2&gt;
  
  
  🔀 Routing Versions
&lt;/h2&gt;

&lt;p&gt;At the API Gateway or router level:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Express routing example&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;v1Router&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./routes/v1&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;v2Router&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./routes/v2&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;use&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/api/v1&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;v1Router&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;use&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/api/v2&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;v2Router&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Or via API Gateway (AWS API Gateway, Kong, Nginx):&lt;/span&gt;
&lt;span class="err"&gt;#&lt;/span&gt; &lt;span class="nx"&gt;nginx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;conf&lt;/span&gt;
&lt;span class="nx"&gt;location&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;api&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;v1&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;proxy_pass&lt;/span&gt; &lt;span class="na"&gt;http&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="c1"&gt;//service-v1/; }&lt;/span&gt;
&lt;span class="nx"&gt;location&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;api&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;v2&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;proxy_pass&lt;/span&gt; &lt;span class="na"&gt;http&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="c1"&gt;//service-v2/; }&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  📋 Breaking vs Non-Breaking Changes
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Change&lt;/th&gt;
&lt;th&gt;Breaking?&lt;/th&gt;
&lt;th&gt;Strategy&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Add new optional field to response&lt;/td&gt;
&lt;td&gt;❌ No&lt;/td&gt;
&lt;td&gt;Safe — clients ignore unknown fields&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Add new optional request param&lt;/td&gt;
&lt;td&gt;❌ No&lt;/td&gt;
&lt;td&gt;Safe — use defaults for old clients&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Remove a field&lt;/td&gt;
&lt;td&gt;✅ Yes&lt;/td&gt;
&lt;td&gt;New version required&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Rename a field&lt;/td&gt;
&lt;td&gt;✅ Yes&lt;/td&gt;
&lt;td&gt;New version required&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Change field type (string→int)&lt;/td&gt;
&lt;td&gt;✅ Yes&lt;/td&gt;
&lt;td&gt;New version required&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Change HTTP method&lt;/td&gt;
&lt;td&gt;✅ Yes&lt;/td&gt;
&lt;td&gt;New version required&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Add required request param&lt;/td&gt;
&lt;td&gt;✅ Yes&lt;/td&gt;
&lt;td&gt;New version required&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Rule:&lt;/strong&gt; If an existing client would break without code changes, it's breaking.&lt;/p&gt;




&lt;h2&gt;
  
  
  🔄 Migration Lifecycle
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;v1 released
    ↓
v2 released (both live simultaneously)
    ↓
v1 deprecated — notice given (3-6 months typical)
    ↓
v1 sunset — clients that haven't migrated break
    ↓
v1 removed from infrastructure
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Deprecation headers&lt;/strong&gt; (tell clients they're on old version):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="k"&gt;HTTP&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="m"&gt;1.1&lt;/span&gt; &lt;span class="m"&gt;200&lt;/span&gt; &lt;span class="ne"&gt;OK&lt;/span&gt;
&lt;span class="na"&gt;Deprecation&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;true&lt;/span&gt;
&lt;span class="na"&gt;Sunset&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Sat, 01 Jan 2026 00:00:00 GMT&lt;/span&gt;
&lt;span class="na"&gt;Link&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;&amp;lt;https://api.example.com/v2/users&amp;gt;; rel="successor-version"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  🏷️ Semantic Versioning for Internal APIs
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;MAJOR.MINOR.PATCH

v2.0.0 → breaking change (new MAJOR version)
v2.1.0 → non-breaking additions (new MINOR version)
v2.1.1 → bug fix (PATCH)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For public REST APIs, only expose MAJOR version in the URL (v1, v2). Minor/patch changes are transparent.&lt;/p&gt;




&lt;h2&gt;
  
  
  🗂️ Version Coexistence Patterns
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Shared Code, Version-Specific Transform
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Core business logic shared&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;userService&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./services/userService&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// v1 controller — old response shape&lt;/span&gt;
&lt;span class="nx"&gt;router&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/v1/users/:id&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;userService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getUser&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;user_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;user_name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt; &lt;span class="c1"&gt;// v1 shape&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="c1"&gt;// v2 controller — new response shape&lt;/span&gt;
&lt;span class="nx"&gt;router&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/v2/users/:id&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;userService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getUser&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;email&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;email&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt; &lt;span class="c1"&gt;// v2 shape&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  ✅ Pros
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Clients can migrate at their own pace&lt;/li&gt;
&lt;li&gt;Enables continuous API evolution without coordination&lt;/li&gt;
&lt;li&gt;Clear contracts per version — easy to document&lt;/li&gt;
&lt;li&gt;URI versioning is CDN-cacheable and easy to debug&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  ❌ Cons
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Maintaining multiple versions increases codebase complexity&lt;/li&gt;
&lt;li&gt;Duplicate code / logic divergence over time&lt;/li&gt;
&lt;li&gt;Clients often stay on old versions longer than expected&lt;/li&gt;
&lt;li&gt;Sunset enforcement requires communicating with all API consumers&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  ⚖️ When to Use / When NOT to Use
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;✅ Use when:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Public APIs with external consumers&lt;/li&gt;
&lt;li&gt;Mobile apps (you can't force users to update immediately)&lt;/li&gt;
&lt;li&gt;B2B APIs where clients control their upgrade schedule&lt;/li&gt;
&lt;li&gt;Any breaking change to a live API&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;❌ Avoid / not needed when:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Internal APIs where you control all consumers (coordinate and deploy together)&lt;/li&gt;
&lt;li&gt;GraphQL APIs (schema evolution handles this differently)&lt;/li&gt;
&lt;li&gt;Early product with no external consumers yet (iterate freely)&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>systemdesign</category>
      <category>api</category>
      <category>backend</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Data Consistency in Microservices — Saga Pattern</title>
      <dc:creator>Gouranga Das Samrat</dc:creator>
      <pubDate>Sat, 01 Aug 2026 02:00:00 +0000</pubDate>
      <link>https://dev.to/gouranga-das-khulna/data-consistency-in-microservices-saga-pattern-4b4d</link>
      <guid>https://dev.to/gouranga-das-khulna/data-consistency-in-microservices-saga-pattern-4b4d</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;One-liner:&lt;/strong&gt; In microservices, you can't use a single database transaction across services — the Saga pattern breaks it into a sequence of local transactions with compensating rollbacks on failure.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  ❓ The Problem: Distributed Transactions
&lt;/h2&gt;

&lt;p&gt;In a monolith, you can wrap everything in one DB transaction:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;BEGIN&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;UPDATE&lt;/span&gt; &lt;span class="n"&gt;orders&lt;/span&gt; &lt;span class="k"&gt;SET&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'confirmed'&lt;/span&gt; &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;UPDATE&lt;/span&gt; &lt;span class="n"&gt;inventory&lt;/span&gt; &lt;span class="k"&gt;SET&lt;/span&gt; &lt;span class="n"&gt;stock&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;stock&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;product_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;INSERT&lt;/span&gt; &lt;span class="k"&gt;INTO&lt;/span&gt; &lt;span class="n"&gt;payments&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;order_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;VALUES&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;99&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;99&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;COMMIT&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;-- all or nothing&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In microservices, each service owns its own DB. &lt;strong&gt;You cannot do a cross-service ACID transaction.&lt;/strong&gt; If the payment service fails after inventory was deducted, you're in an inconsistent state.&lt;/p&gt;




&lt;h2&gt;
  
  
  🎭 The Saga Pattern
&lt;/h2&gt;

&lt;p&gt;A Saga is a sequence of local transactions. Each step publishes an event triggering the next. If any step fails, &lt;strong&gt;compensating transactions&lt;/strong&gt; undo the previous steps.&lt;/p&gt;

&lt;h3&gt;
  
  
  Example: E-commerce Order Flow
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Step 1: Order Service      → Create order (PENDING)
Step 2: Inventory Service  → Reserve items
Step 3: Payment Service    → Charge customer
Step 4: Shipping Service   → Schedule delivery
Step 5: Order Service      → Mark order CONFIRMED
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Happy path ✅:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Order Created → Items Reserved → Payment Charged → Shipping Scheduled → Done ✅
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Failure at Payment ❌:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Order Created → Items Reserved → Payment FAILED
                                       ↓
                          Compensate: Release inventory
                          Compensate: Cancel order
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  🏗️ Two Implementation Styles
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Choreography (Event-driven, decentralized)
&lt;/h3&gt;

&lt;p&gt;Each service listens for events and reacts. No central coordinator.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;OrderService    ──publishes──► "OrderCreated"
InventoryService ──listens──► reserves stock, publishes "StockReserved"
PaymentService  ──listens──► charges card, publishes "PaymentProcessed"
ShippingService ──listens──► schedules delivery, publishes "Shipped"

On failure:
PaymentService publishes "PaymentFailed"
InventoryService listens, releases stock, publishes "StockReleased"
OrderService listens, marks order CANCELLED
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Pros:&lt;/strong&gt; Simple, decoupled, no single point of failure&lt;br&gt;&lt;br&gt;
&lt;strong&gt;Cons:&lt;/strong&gt; Hard to track overall saga state, cyclic dependencies possible&lt;/p&gt;


&lt;h3&gt;
  
  
  2. Orchestration (Centralized coordinator)
&lt;/h3&gt;

&lt;p&gt;A dedicated Saga Orchestrator tells each service what to do.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;                    ┌─────────────────────┐
                    │   Saga Orchestrator  │
                    │   (Order Saga)       │
                    └──────────┬──────────┘
                               │
         ┌─────────────────────┼──────────────────────┐
         ▼                     ▼                       ▼
  InventoryService       PaymentService          ShippingService
  "Reserve stock"        "Charge $99"            "Ship to addr"
  → "Reserved" ✅        → "Failed" ❌           (not called)
         │                     │
         └─────────────────────┘
                    Orchestrator receives "Failed"
                    → tells InventoryService: "Release stock"
                    → marks saga FAILED
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Pros:&lt;/strong&gt; Clear saga state, easy to audit and debug&lt;br&gt;&lt;br&gt;
&lt;strong&gt;Cons:&lt;/strong&gt; Orchestrator is a bottleneck / SPOF if not designed carefully&lt;/p&gt;


&lt;h2&gt;
  
  
  🆚 Choreography vs Orchestration
&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;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;Control&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Decentralized&lt;/td&gt;
&lt;td&gt;Centralized&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;Loose (events)&lt;/td&gt;
&lt;td&gt;Tighter (direct calls)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Visibility&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Hard to trace end-to-end&lt;/td&gt;
&lt;td&gt;Easy — one place to look&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Complexity&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Low per-service, high overall&lt;/td&gt;
&lt;td&gt;High orchestrator logic&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Best for&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;3-4 step sagas&lt;/td&gt;
&lt;td&gt;Complex, many-step flows&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;


&lt;h2&gt;
  
  
  💾 Saga State Storage
&lt;/h2&gt;

&lt;p&gt;Track saga state to survive crashes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;sagas&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="n"&gt;UUID&lt;/span&gt; &lt;span class="k"&gt;PRIMARY&lt;/span&gt; &lt;span class="k"&gt;KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="nb"&gt;VARCHAR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;          &lt;span class="c1"&gt;-- 'order_saga'&lt;/span&gt;
  &lt;span class="n"&gt;current_step&lt;/span&gt; &lt;span class="nb"&gt;VARCHAR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;  &lt;span class="c1"&gt;-- 'awaiting_payment'&lt;/span&gt;
  &lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="nb"&gt;VARCHAR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;        &lt;span class="c1"&gt;-- 'IN_PROGRESS', 'COMPLETED', 'FAILED'&lt;/span&gt;
  &lt;span class="n"&gt;payload&lt;/span&gt; &lt;span class="n"&gt;JSONB&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;             &lt;span class="c1"&gt;-- order details&lt;/span&gt;
  &lt;span class="n"&gt;created_at&lt;/span&gt; &lt;span class="nb"&gt;TIMESTAMP&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;updated_at&lt;/span&gt; &lt;span class="nb"&gt;TIMESTAMP&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  ✅ Pros
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Maintains data consistency without distributed transactions&lt;/li&gt;
&lt;li&gt;Works with polyglot persistence (each service uses its own DB)&lt;/li&gt;
&lt;li&gt;Choreography scales well horizontally&lt;/li&gt;
&lt;li&gt;Orchestration provides clear audit trail&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  ❌ Cons
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Eventual consistency — data temporarily inconsistent between steps&lt;/li&gt;
&lt;li&gt;Compensating transactions are complex to implement correctly&lt;/li&gt;
&lt;li&gt;Hard to debug when something fails mid-saga&lt;/li&gt;
&lt;li&gt;Idempotency required for every step (retries happen)&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  ⚖️ When to Use / When NOT to Use
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;✅ Use when:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Multi-service workflows that must be atomic (order → payment → shipping)&lt;/li&gt;
&lt;li&gt;Different services own different databases&lt;/li&gt;
&lt;li&gt;Long-running business processes (insurance claims, loan approvals)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;❌ Avoid when:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Single service with one DB (just use a DB transaction)&lt;/li&gt;
&lt;li&gt;Simple async operations that don't need rollback&lt;/li&gt;
&lt;li&gt;You can redesign to avoid the distributed write entirely&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>systemdesign</category>
      <category>devops</category>
      <category>backend</category>
      <category>microservices</category>
    </item>
    <item>
      <title>Shipping the Chaos: One Contract, Three Codegens, and a Docker Gotcha Hiding in Plain Sight</title>
      <dc:creator>Gouranga Das Samrat</dc:creator>
      <pubDate>Tue, 28 Jul 2026 04:00:00 +0000</pubDate>
      <link>https://dev.to/gouranga-das-khulna/shipping-the-chaos-one-contract-three-codegens-and-a-docker-gotcha-hiding-in-plain-sight-3mcn</link>
      <guid>https://dev.to/gouranga-das-khulna/shipping-the-chaos-one-contract-three-codegens-and-a-docker-gotcha-hiding-in-plain-sight-3mcn</guid>
      <description>&lt;p&gt;&lt;em&gt;This is the final post in "The Muse of Microservices," a series on the system design behind &lt;a href="https://github.com/GourangaDasSamrat/Polyhymnia" rel="noopener noreferrer"&gt;Polyhymnia&lt;/a&gt;. Parts 1–3 covered the architecture, the Go gateway, and the Rust/C++ backends. This post covers what holds it all together and how it ships — plus one bug hunt you can go do yourself.&lt;/em&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;How Polyhymnia's single protobuf contract turns into three different generated codebases, how the project ships to Docker, and a loopback-address gotcha worth hunting down yourself.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  One file, three languages, zero hand-written serialization
&lt;/h2&gt;

&lt;p&gt;Everything about how these services talk to each other traces back to a single 40-line file: &lt;code&gt;proto/quote.proto&lt;/code&gt;. It defines two services, five messages, and three RPCs, and it is the &lt;strong&gt;only&lt;/strong&gt; place any of that is defined. Nobody hand-writes a JSON schema for the Rust service and a separate struct for the Go client and hopes they stay in sync — that's exactly the kind of manual synchronization that silently rots the moment someone forgets to update one side.&lt;/p&gt;

&lt;p&gt;Instead, each language generates its own stubs from the same source of truth, through three genuinely different pipelines:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Rust&lt;/strong&gt; does it automatically, on every &lt;code&gt;cargo build&lt;/code&gt;, via a &lt;code&gt;build.rs&lt;/code&gt; script that calls &lt;code&gt;tonic_build::compile_protos(...)&lt;/code&gt;. You never run a separate codegen step — it's baked into the normal build.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;C++&lt;/strong&gt; does it through a CMake &lt;code&gt;add_custom_command&lt;/code&gt; that shells out to &lt;code&gt;protoc&lt;/code&gt; with the gRPC C++ plugin, generating &lt;code&gt;.pb.cc&lt;/code&gt;/&lt;code&gt;.pb.h&lt;/code&gt; files into the build directory before the actual source is even compiled.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Go&lt;/strong&gt; is the odd one out — its stubs are generated explicitly via a &lt;code&gt;just proto-go&lt;/code&gt; recipe that invokes &lt;code&gt;protoc-gen-go&lt;/code&gt; and &lt;code&gt;protoc-gen-go-grpc&lt;/code&gt;, rather than as an automatic build step.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Three different mechanisms, one contract, zero drift possible between them — because there's nothing to drift. Change a field in the &lt;code&gt;.proto&lt;/code&gt; file and every language's next build simply reflects it, or fails to compile until you update the code that used the old shape. That failure mode, "won't compile" instead of "silently breaks in production," is the entire value proposition of contract-first API design in one sentence.&lt;/p&gt;

&lt;h2&gt;
  
  
  A small but telling asymmetry: reflection
&lt;/h2&gt;

&lt;p&gt;The C++ service turns on gRPC server reflection:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight cpp"&gt;&lt;code&gt;&lt;span class="n"&gt;grpc&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;reflection&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;InitProtoReflectionServerBuilderPlugin&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;which means a tool like &lt;code&gt;grpcurl&lt;/code&gt; can introspect the &lt;code&gt;Randomizer&lt;/code&gt; service's methods and message shapes at runtime, with no &lt;code&gt;.proto&lt;/code&gt; file in hand. The Rust service doesn't do this — &lt;code&gt;tonic-reflection&lt;/code&gt; isn't even in its dependency list. Neither choice is wrong; reflection is a debugging convenience, not a requirement, and it's genuinely useful to notice that two services in the same codebase made different calls about it. It's a good habit to carry into your own projects: reflection on your internal services costs you almost nothing and saves a &lt;code&gt;.proto&lt;/code&gt; file scavenger hunt the next time you're debugging with &lt;code&gt;grpcurl&lt;/code&gt; at 2am.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;code&gt;justfile&lt;/code&gt;: one command runner for three build systems
&lt;/h2&gt;

&lt;p&gt;Cargo, CMake, and the Go toolchain each have their own build model, their own flags, and their own idea of what "clean" means. Polyhymnia flattens all three behind a single &lt;code&gt;justfile&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;just build        &lt;span class="c"&gt;# build-rust + build-cpp + build-go&lt;/span&gt;
just run           &lt;span class="c"&gt;# build everything, then launch all four processes&lt;/span&gt;
just proto-go       &lt;span class="c"&gt;# regenerate Go stubs&lt;/span&gt;
just clean          &lt;span class="c"&gt;# wipe every language's build artifacts&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is a small but real answer to a real problem: polyglot repositories have an onboarding cost that monoglot repositories simply don't, because every contributor has to context-switch between build systems just to get the thing running. A single command runner doesn't eliminate that cost, but it hides it behind one consistent vocabulary — &lt;code&gt;just build&lt;/code&gt;, &lt;code&gt;just run&lt;/code&gt;, &lt;code&gt;just clean&lt;/code&gt; — regardless of which language is underneath. It's the kind of unglamorous tooling decision that never shows up in an architecture diagram and saves everyone real time anyway.&lt;/p&gt;

&lt;h2&gt;
  
  
  Packaging: four Dockerfiles, three of them multi-stage
&lt;/h2&gt;

&lt;p&gt;Each service ships with its own Dockerfile, and three of the four follow the same shape: a fat &lt;strong&gt;builder&lt;/strong&gt; stage with the full compiler toolchain, followed by a slim &lt;strong&gt;runtime&lt;/strong&gt; stage that copies out only the finished binary.&lt;/p&gt;

&lt;p&gt;The Rust image, for instance, compiles inside &lt;code&gt;rust:latest&lt;/code&gt; (with &lt;code&gt;protobuf-compiler&lt;/code&gt; installed for &lt;code&gt;tonic-build&lt;/code&gt;), then copies just the resulting binary into a bare &lt;code&gt;debian:bookworm-slim&lt;/code&gt; image. The Go image does the same trick even more aggressively — it builds with &lt;code&gt;CGO_ENABLED=0&lt;/code&gt; and ships the result into &lt;code&gt;alpine:3.18&lt;/code&gt;, ending up with a runtime image that has no build tooling, no source code, and a dramatically smaller attack surface than the image that built it. The frontend, having nothing to compile, skips all of this and is just static files dropped straight into &lt;code&gt;nginx:stable-alpine&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Multi-stage builds like this are standard practice for a reason: your production image should contain exactly what's needed to run the service and nothing that was needed to &lt;em&gt;build&lt;/em&gt; it. Every extra tool left in a runtime image is one more thing that can have a vulnerability, and one more reason the image is bigger than it needs to be.&lt;/p&gt;

&lt;h2&gt;
  
  
  Two compose files, two audiences
&lt;/h2&gt;

&lt;p&gt;The repo ships &lt;strong&gt;two&lt;/strong&gt; separate &lt;code&gt;docker-compose&lt;/code&gt; files, and the distinction between them is worth calling out because it maps to two genuinely different users:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;docker-compose.yml&lt;/code&gt; builds every image locally from source — the right choice for a contributor who's actively changing code.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;docker-compose.images.yml&lt;/code&gt; instead points at pre-built images on Docker Hub (with a &lt;code&gt;&amp;lt;VERSION&amp;gt;&lt;/code&gt; placeholder to fill in) — the right choice for someone who just wants to run the finished thing without owning a full multi-language toolchain.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Same four services, same ports, same topology — just a different answer to "where does the image come from." It's a small piece of design that quietly respects that "contributor" and "user" are different personas with different needs.&lt;/p&gt;

&lt;h2&gt;
  
  
  The gotcha: what "127.0.0.1" means depends on where you're standing
&lt;/h2&gt;

&lt;p&gt;Here's where it gets genuinely interesting, and it connects directly back to something we flagged in Part 2.&lt;/p&gt;

&lt;p&gt;Recall that the Go gateway dials its two backends at hardcoded addresses:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="n"&gt;rustDbAddr&lt;/span&gt;    &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"127.0.0.1:50051"&lt;/span&gt;
&lt;span class="n"&gt;cppEngineAddr&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"127.0.0.1:50052"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and the Rust service binds to:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;LISTEN_ADDR&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"127.0.0.1:50051"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Both of those are entirely correct for the workflow the docs lead with first — &lt;code&gt;just run&lt;/code&gt;, where every process launches directly on your machine and shares one network stack. On localhost, &lt;code&gt;127.0.0.1&lt;/code&gt; unambiguously means "this machine," and every service can reach every other service through it without issue.&lt;/p&gt;

&lt;p&gt;Docker changes the ground under that assumption. Each container in a Compose stack gets &lt;strong&gt;its own network namespace&lt;/strong&gt; — its own private, isolated &lt;code&gt;127.0.0.1&lt;/code&gt; that refers only to itself. When the &lt;code&gt;go-gateway&lt;/code&gt; container dials &lt;code&gt;127.0.0.1:50051&lt;/code&gt;, it is not reaching the &lt;code&gt;rust-db&lt;/code&gt; container at all; it's trying to reach port 50051 on &lt;em&gt;itself&lt;/em&gt;, where nothing is listening. Cross-container traffic in Compose is meant to travel over the service name instead — &lt;code&gt;rust-db:50051&lt;/code&gt;, resolved through Docker's built-in DNS on the shared Compose network — which is precisely what &lt;code&gt;depends_on&lt;/code&gt; in the compose file gestures at but doesn't, on its own, wire up.&lt;/p&gt;

&lt;p&gt;Interestingly, the codebase already contains a working example of the container-friendly pattern, sitting right next to the problem: the C++ engine binds to&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight cpp"&gt;&lt;code&gt;&lt;span class="k"&gt;constexpr&lt;/span&gt; &lt;span class="kt"&gt;char&lt;/span&gt; &lt;span class="n"&gt;kListenAddress&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"0.0.0.0:50052"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;0.0.0.0&lt;/code&gt; means "listen on every network interface," which &lt;em&gt;is&lt;/em&gt; reachable from sibling containers, unlike Rust's loopback-only bind. So the two backend services in this project actually demonstrate both the wrong pattern and the right one for containerized binding, and the fix on the Go side would follow the same idea: make the dial addresses configurable — an environment variable like &lt;code&gt;RUST_DB_ADDR&lt;/code&gt;, defaulting to &lt;code&gt;127.0.0.1:50051&lt;/code&gt; for local runs but overridable to &lt;code&gt;rust-db:50051&lt;/code&gt; inside Compose — rather than baking one deployment mode's assumption into a compile-time constant.&lt;/p&gt;

&lt;p&gt;This is genuinely worth doing yourself as an exercise if you clone the repo: spin up &lt;code&gt;docker-compose up&lt;/code&gt;, watch what happens, and then go fix the addressing to make it actually work across containers. It's a small, self-contained, extremely realistic bug — the exact category of "worked perfectly on my machine, broke the second it left it" issue that shows up constantly in real infrastructure work, just shrunk down to a size you can fully understand and fix in one sitting.&lt;/p&gt;

&lt;p&gt;(And it's a nice full-circle moment for the series: the fixed-interval retry loop we covered in Part 2 is precisely the mechanism that would cover the &lt;em&gt;timing&lt;/em&gt; half of a multi-container startup — Compose's &lt;code&gt;depends_on&lt;/code&gt; only waits for a container to start, not for the service inside it to actually be ready, and the gateway's own retry-and-block dial logic is what papers over that gap. It just needs to be dialing the right address to begin with.)&lt;/p&gt;

&lt;h2&gt;
  
  
  What you'd change to make it "real"
&lt;/h2&gt;

&lt;p&gt;None of this needs fixing to serve its actual purpose — Polyhymnia is upfront about being satire with genuinely good bones, not a production template. But it's worth closing the series by naming, plainly, what separates "excellent teaching example" from "thing you'd actually run at scale," because that list is itself a useful mental checklist:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Externalize configuration.&lt;/strong&gt; Addresses, ports, and timeouts as environment variables instead of constants, so the same binary behaves correctly across &lt;code&gt;just run&lt;/code&gt;, Compose, and a real orchestrator without a recompile.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Add a circuit breaker.&lt;/strong&gt; Right now a slow or failing backend just eats into the gateway's fixed timeout budget on every request; a breaker would stop hammering a backend that's already down instead of retrying it into the ground.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Move from fixed-interval to backoff-with-jitter retries&lt;/strong&gt; once more than one caller could plausibly be retrying against the same dependency at once.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Pool database connections&lt;/strong&gt; instead of guarding a single one behind a mutex, once concurrent load is real rather than theoretical.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Encrypt service-to-service traffic.&lt;/strong&gt; Every gRPC channel here uses &lt;code&gt;insecure.NewCredentials()&lt;/code&gt; — completely fine on a trusted loopback interface, and the first thing to change the moment these services cross a network you don't fully control.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That list isn't a criticism of the project — it's the reward for having read it closely enough to write it. A system small enough to fully understand is also a system small enough to see clearly what growing it up would actually require.&lt;/p&gt;

&lt;h2&gt;
  
  
  Closing the loop
&lt;/h2&gt;

&lt;p&gt;Four services, one contract, five hops, and one very committed random number generator — all in service of a button that returns a sentence and a name. That gap between the size of the problem and the size of the solution was always the point, and if you've followed this series through, you now know exactly what's happening at every one of those hops and why.&lt;/p&gt;

&lt;p&gt;Go clone it, click the button a few times, and then go fix that Docker networking issue yourself: &lt;strong&gt;&lt;a href="https://github.com/GourangaDasSamrat/Polyhymnia" rel="noopener noreferrer"&gt;GourangaDasSamrat/Polyhymnia&lt;/a&gt;&lt;/strong&gt;.&lt;/p&gt;

</description>
      <category>docker</category>
      <category>grpc</category>
      <category>devops</category>
      <category>systemdesign</category>
    </item>
    <item>
      <title>Trust No One: Rust's Safety Layer and C++'s Beautifully Unnecessary Randomness</title>
      <dc:creator>Gouranga Das Samrat</dc:creator>
      <pubDate>Mon, 27 Jul 2026 04:00:00 +0000</pubDate>
      <link>https://dev.to/gouranga-das-khulna/trust-no-one-rusts-safety-layer-and-cs-beautifully-unnecessary-randomness-4oon</link>
      <guid>https://dev.to/gouranga-das-khulna/trust-no-one-rusts-safety-layer-and-cs-beautifully-unnecessary-randomness-4oon</guid>
      <description>&lt;p&gt;&lt;em&gt;This is Part 3 of "The Muse of Microservices," a series on the system design behind &lt;a href="https://github.com/GourangaDasSamrat/Polyhymnia" rel="noopener noreferrer"&gt;Polyhymnia&lt;/a&gt;. Part 2 covered the Go gateway that calls both services you're about to meet.&lt;/em&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Two backend services, two philosophies: how Polyhymnia's Rust database owner protects a single SQLite file, and how its C++ engine turns picking one array index into a systems-programming set piece.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Two services live behind the Go gateway, and they couldn't have more different personalities. One holds the only piece of mutable state in the entire system and treats that responsibility like a vault. The other holds nothing at all and treats a coin flip like a research paper. Put side by side, they're a compact lesson in two of the most fundamental axes of service design: &lt;strong&gt;who owns data&lt;/strong&gt;, and &lt;strong&gt;what it costs to keep a service stateless&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Rust: the database's only bodyguard
&lt;/h2&gt;

&lt;p&gt;The Rust service's full title in the README is "Database Manager &amp;amp; Safety Layer," and both halves of that name are backed by real code, not vibes.&lt;/p&gt;

&lt;h3&gt;
  
  
  One owner, no exceptions
&lt;/h3&gt;

&lt;p&gt;Exactly one service in Polyhymnia is allowed to open &lt;code&gt;quotes.db&lt;/code&gt;. Not the Go gateway, not the C++ engine — only Rust, and every other service reaches the data exclusively through two gRPC methods: &lt;code&gt;GetAllIds&lt;/code&gt; and &lt;code&gt;GetQuoteById&lt;/code&gt;. This is the &lt;strong&gt;database-per-service&lt;/strong&gt; principle in its purest, smallest possible form: shared databases create a hidden coupling between every service that touches them, because a schema change in one now has to be coordinated with every other reader and writer, whether or not they ever talk to each other directly. Give exactly one service exclusive ownership of the data, and that coupling problem simply doesn't exist. Everyone else negotiates through an API instead of a schema.&lt;/p&gt;

&lt;h3&gt;
  
  
  Parameters, not string concatenation
&lt;/h3&gt;

&lt;p&gt;The "safety" in the service's name isn't just branding — it shows up directly in how every query is built:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="nf"&gt;.query_row&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s"&gt;"SELECT quote, author FROM quotes WHERE id = ?1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nd"&gt;params!&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="n"&gt;row&lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="cm"&gt;/* ... */&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That &lt;code&gt;?1&lt;/code&gt; placeholder, filled in via &lt;code&gt;rusqlite::params!&lt;/code&gt;, is a parameterized query — the value is bound separately from the SQL text rather than spliced into a string. It's a one-line habit that closes off SQL injection as a category entirely, because the database driver never has to guess where a query ends and untrusted data begins. Every query in the service follows this pattern; there isn't a single interpolated string anywhere in the SQL layer. For a service whose entire reason for existing is to be the trustworthy custodian of the one file everyone else depends on, that's not a stylistic footnote — it's the whole job description.&lt;/p&gt;

&lt;h3&gt;
  
  
  The concurrency model, and its ceiling
&lt;/h3&gt;

&lt;p&gt;The connection itself is wrapped like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="n"&gt;QuoteDbService&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Arc&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Mutex&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Connection&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;One SQLite connection, shared across every request, guarded by a &lt;code&gt;tokio::sync::Mutex&lt;/code&gt;. In practice, that means every database operation the service performs — every &lt;code&gt;GetAllIds&lt;/code&gt;, every &lt;code&gt;GetQuoteById&lt;/code&gt; — is serialized. Only one query runs at a time, full stop, no matter how many requests arrive concurrently.&lt;/p&gt;

&lt;p&gt;That's a completely reasonable choice for this project, and it's worth understanding &lt;em&gt;why&lt;/em&gt; it's reasonable rather than just accepting it. &lt;code&gt;Arc&amp;lt;Mutex&amp;lt;T&amp;gt;&amp;gt;&lt;/code&gt; is the simplest possible way to share mutable state safely across async tasks in Rust — it trades throughput for correctness and simplicity, which is exactly the right trade when your query load is "occasionally, one at a time, from a demo app." It would become the ceiling on this service's scalability the moment real concurrent load showed up — a connection pool (handing out several connections instead of guarding one) is the natural next step if that ever became a problem. Knowing which lever to pull, and when it's actually worth pulling, is most of what capacity planning is.&lt;/p&gt;

&lt;p&gt;Interestingly, the service still turns on SQLite's write-ahead log:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;String&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="nf"&gt;.query_row&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"PRAGMA journal_mode = WAL"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[],&lt;/span&gt; &lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="n"&gt;row&lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="n"&gt;row&lt;/span&gt;&lt;span class="nf"&gt;.get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Given that the app itself already serializes every query through the mutex, that might look redundant — and for traffic that arrives &lt;em&gt;only&lt;/em&gt; through this service, it is. But WAL mode also changes how SQLite behaves when something &lt;em&gt;outside&lt;/em&gt; the app touches the same file — a &lt;code&gt;sqlite3&lt;/code&gt; CLI session opened for debugging, for instance, which the project's own troubleshooting docs describe doing. WAL lets readers avoid blocking on a writer at the file level, not just at the application level, which is a nice bit of defense-in-depth for a database file that's meant to have exactly one legitimate writer but might occasionally get an uninvited guest.&lt;/p&gt;

&lt;h3&gt;
  
  
  Idempotent bootstrapping
&lt;/h3&gt;

&lt;p&gt;On first launch, the service creates its own schema and seeds itself with ten quotes — but only if the table is empty:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;count&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;i64&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="nf"&gt;.query_row&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"SELECT COUNT(*) FROM quotes"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[],&lt;/span&gt; &lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="n"&gt;row&lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="n"&gt;row&lt;/span&gt;&lt;span class="nf"&gt;.get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;count&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="cm"&gt;/* seed */&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;CREATE TABLE IF NOT EXISTS&lt;/code&gt; plus a count-gated seed step means the service can be started, killed, and restarted indefinitely without ever duplicating data or crashing on an already-initialized database. It's a self-contained, self-healing bootstrap — a tiny version of what a real migrations framework buys you at scale, running here with nothing but a conditional and a transaction.&lt;/p&gt;

&lt;h2&gt;
  
  
  C++: a coin flip with a PhD's worth of ceremony
&lt;/h2&gt;

&lt;p&gt;Then there's the Randomizer. Its interface is one RPC — &lt;code&gt;SelectRandomId(IdList) -&amp;gt; SelectedId&lt;/code&gt; — and its job, stated as plainly as possible, is: &lt;em&gt;given a list of numbers, return one of them.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Here's how it actually does that:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight cpp"&gt;&lt;code&gt;&lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;random_device&lt;/span&gt; &lt;span class="n"&gt;rd&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="kt"&gt;uint64_t&lt;/span&gt; &lt;span class="n"&gt;hi&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;static_cast&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;uint64_t&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rd&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
&lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="kt"&gt;uint64_t&lt;/span&gt; &lt;span class="n"&gt;lo&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;static_cast&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;uint64_t&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rd&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
&lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="kt"&gt;uint64_t&lt;/span&gt; &lt;span class="n"&gt;seed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;hi&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;lo&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;mt19937_64&lt;/span&gt; &lt;span class="nf"&gt;engine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;seed&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;uniform_int_distribution&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="kt"&gt;size_t&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;dist&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;count&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two independent pulls from the OS's entropy source, bit-shifted together into a single 64-bit seed, fed into a Mersenne Twister engine, which is then sampled through a uniform distribution to get an index. And then, instead of just calling &lt;code&gt;.at(index)&lt;/code&gt; like a person in a hurry:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight cpp"&gt;&lt;code&gt;&lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="kt"&gt;int64_t&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;base&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;ids&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="kt"&gt;int64_t&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;target&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;base&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="kt"&gt;int64_t&lt;/span&gt; &lt;span class="n"&gt;selected_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;target&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;...it walks a raw pointer to the target element by hand. This is, transparently, a joke — the README says as much, and one call to &lt;code&gt;dist(engine)&lt;/code&gt; alone would have been more than sufficient to pick a number out of a list of a dozen IDs. But the ceremony is worth taking seriously for a second, because every individual piece of it is a real pattern lifted from a context where it genuinely matters:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;random_device&lt;/code&gt; seeding a fast PRNG&lt;/strong&gt; is exactly how you'd build a cryptographically-aware random number generator: pull true entropy once, expensively, from the OS, then use it to seed a much faster deterministic generator for everything after. It's the right shape, applied to a problem that never needed it.&lt;/li&gt;
&lt;li&gt;One footnote worth knowing regardless: the C++ standard never actually &lt;em&gt;guarantees&lt;/em&gt; &lt;code&gt;std::random_device&lt;/code&gt; is non-deterministic. Some standard library implementations fall back to a deterministic PRNG when no hardware entropy source is available on the platform. It's the kind of assumption that's safe 99% of the time and exactly the kind of thing worth double-checking before you build something security-sensitive on top of it.&lt;/li&gt;
&lt;li&gt;The pointer arithmetic is functionally identical to &lt;code&gt;operator[]&lt;/code&gt; — the compiler will very likely generate the same instructions either way — but it's a nice reminder that in C++, "correct" and "idiomatic" are two different bars, and this project is having fun clearing the first one while cheerfully ignoring the second.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Stateless, and proud of it
&lt;/h3&gt;

&lt;p&gt;The one thing the Randomizer is genuinely, unironically good architecture for: it holds &lt;strong&gt;no state whatsoever&lt;/strong&gt;. It doesn't know what a quote is, doesn't know what a database is, and doesn't remember anything between calls. Its only input is a list of integers; its only output is one of them.&lt;/p&gt;

&lt;p&gt;That statelessness is the real payoff, and it's worth contrasting directly against the Rust service sitting right next to it. Because the Randomizer carries no data between requests, you could run ten copies of it behind a load balancer tomorrow with zero coordination between them — no shared cache to invalidate, no lock to contend over, nothing to keep in sync. The Rust service, by contrast, can't be casually replicated at all without first solving the much harder problem of who owns the one SQLite file. Two services, two completely different scaling stories, sitting three lines of Go apart in the gateway's handler. That contrast — stateless services scale by copying, stateful services scale by coordinating — is one of the most load-bearing ideas in distributed systems, and Polyhymnia hands it to you as a two-service case study you can read in an afternoon.&lt;/p&gt;

&lt;p&gt;It also validates its own input, which is easy to overlook: an empty ID list returns &lt;code&gt;INVALID_ARGUMENT&lt;/code&gt; rather than crashing or silently returning garbage. Small, boring, and exactly what you want from a service sitting at a trust boundary.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's next
&lt;/h2&gt;

&lt;p&gt;We've now met all four services and the one contract that lets them talk to each other. Part 4 zooms out to that contract itself — one &lt;code&gt;.proto&lt;/code&gt; file feeding three independent codegen pipelines — and follows the project the rest of the way to a running container stack, including a networking subtlety in the Docker setup that's easy to miss until you go looking for it.&lt;/p&gt;

</description>
      <category>rust</category>
      <category>cpp</category>
      <category>systemdesign</category>
      <category>sqlite</category>
    </item>
    <item>
      <title>Containers &amp; Docker</title>
      <dc:creator>Gouranga Das Samrat</dc:creator>
      <pubDate>Sun, 26 Jul 2026 02:00:00 +0000</pubDate>
      <link>https://dev.to/gouranga-das-khulna/containers-docker-5le</link>
      <guid>https://dev.to/gouranga-das-khulna/containers-docker-5le</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;One-liner:&lt;/strong&gt; Containers package your application and all its dependencies into one portable unit that runs identically on any machine — eliminating "works on my machine" problems.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  📌 The Problem Before Containers
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Dev machine:    Node 18, npm 9, Ubuntu 22
Staging server: Node 16, npm 8, CentOS 7
Production:     Node 14, npm 6, Amazon Linux 2

Result: App works in dev, breaks in prod 🔥
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  💡 Containers vs Virtual Machines
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Virtual Machine (VM)
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[App A] [App B] [App C]
[Guest OS] [Guest OS] [Guest OS]   ← each VM has full OS (GBs)
[Hypervisor (VMware, KVM)]
[Host OS]
[Hardware]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Heavy: ~1 GB+ per VM, slow to start (minutes)&lt;/p&gt;

&lt;h3&gt;
  
  
  Container
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[App A] [App B] [App C]
[Container Runtime (Docker)]       ← share host OS kernel
[Host OS]
[Hardware]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Lightweight: ~10-100 MB per container, starts in milliseconds&lt;/p&gt;




&lt;h2&gt;
  
  
  🐳 Docker Core Concepts
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Image
&lt;/h3&gt;

&lt;p&gt;A read-only template — like a blueprint/snapshot of your app + dependencies.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="c"&gt;# Dockerfile — recipe to build an image&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; node:18-alpine          # base image&lt;/span&gt;
&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /app                 # working directory&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; package*.json ./        # copy dependency files&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;npm ci &lt;span class="nt"&gt;--production&lt;/span&gt;      &lt;span class="c"&gt;# install dependencies&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; . .                     # copy source code&lt;/span&gt;
&lt;span class="k"&gt;EXPOSE&lt;/span&gt;&lt;span class="s"&gt; 3000                  # document port&lt;/span&gt;
&lt;span class="k"&gt;CMD&lt;/span&gt;&lt;span class="s"&gt; ["node", "server.js"]    # start command&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Build: &lt;code&gt;docker build -t my-app:1.0 .&lt;/code&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Container
&lt;/h3&gt;

&lt;p&gt;A running instance of an image.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker run &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-p&lt;/span&gt; 3000:3000 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="nv"&gt;DATABASE_URL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;postgres://... &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--name&lt;/span&gt; my-app &lt;span class="se"&gt;\&lt;/span&gt;
  my-app:1.0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Registry
&lt;/h3&gt;

&lt;p&gt;Where images are stored and shared.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker push myrepo/my-app:1.0   &lt;span class="c"&gt;# push to Docker Hub / ECR&lt;/span&gt;
docker pull myrepo/my-app:1.0   &lt;span class="c"&gt;# pull on another machine&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Popular registries: Docker Hub, AWS ECR, GitHub Container Registry, GCR&lt;/p&gt;




&lt;h2&gt;
  
  
  📁 Dockerfile Best Practices
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Layer Caching — Order Matters!
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="c"&gt;# ❌ Bad — any code change invalidates npm install layer&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; node:18-alpine&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; . .&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;npm ci

&lt;span class="c"&gt;# ✅ Good — npm install layer cached unless package.json changes&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; node:18-alpine&lt;/span&gt;
&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /app&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; package*.json ./   ← copy just package files first&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;npm ci              ← this layer cached &lt;span class="k"&gt;if &lt;/span&gt;package.json unchanged
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; . .                ← code changes only bust this layer&lt;/span&gt;
&lt;span class="k"&gt;CMD&lt;/span&gt;&lt;span class="s"&gt; ["node", "server.js"]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Multi-Stage Build (Small Final Image)
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="c"&gt;# Stage 1: Build&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;node:18&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;builder&lt;/span&gt;
&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /app&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; . .&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;npm ci &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; npm run build

&lt;span class="c"&gt;# Stage 2: Production (only compiled output)&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;node:18-alpine&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;production&lt;/span&gt;
&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /app&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --from=builder /app/dist ./dist&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --from=builder /app/node_modules ./node_modules&lt;/span&gt;
&lt;span class="k"&gt;CMD&lt;/span&gt;&lt;span class="s"&gt; ["node", "dist/server.js"]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Result: Final image is tiny (no dev tools, no source, no test files).&lt;/p&gt;

&lt;h3&gt;
  
  
  .dockerignore
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight properties"&gt;&lt;code&gt;&lt;span class="err"&gt;node_modules&lt;/span&gt;
&lt;span class="err"&gt;.git&lt;/span&gt;
&lt;span class="err"&gt;*.test.js&lt;/span&gt;
&lt;span class="err"&gt;.env&lt;/span&gt;
&lt;span class="err"&gt;dist&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  🔧 Docker Compose — Multi-Container Local Dev
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# docker-compose.yml&lt;/span&gt;
&lt;span class="na"&gt;version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;3.8"&lt;/span&gt;
&lt;span class="na"&gt;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;.&lt;/span&gt;
    &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;3000:3000"&lt;/span&gt;
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;DATABASE_URL=postgres://postgres:pass@db:5432/mydb&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;REDIS_URL=redis://redis:6379&lt;/span&gt;
    &lt;span class="na"&gt;depends_on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;db&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;redis&lt;/span&gt;

  &lt;span class="na"&gt;db&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;postgres:15&lt;/span&gt;
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;POSTGRES_PASSWORD&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;pass&lt;/span&gt;
      &lt;span class="na"&gt;POSTGRES_DB&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;mydb&lt;/span&gt;
    &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;postgres_data:/var/lib/postgresql/data&lt;/span&gt;

  &lt;span class="na"&gt;redis&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;redis:7-alpine&lt;/span&gt;

&lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;postgres_data&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Start everything: &lt;code&gt;docker compose up -d&lt;/code&gt;&lt;br&gt;&lt;br&gt;
Stop: &lt;code&gt;docker compose down&lt;/code&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  ☸️ Container Orchestration: Kubernetes (K8s)
&lt;/h2&gt;

&lt;p&gt;Docker runs containers on ONE machine. In production you need many machines.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Kubernetes&lt;/strong&gt; orchestrates containers across a cluster of machines.&lt;/p&gt;

&lt;h3&gt;
  
  
  Key K8s Concepts
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Concept&lt;/th&gt;
&lt;th&gt;What It Is&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Pod&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Smallest unit — one or more containers&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Deployment&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Manages pod replicas, rolling updates&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Service&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Stable network endpoint for a set of pods&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Ingress&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;HTTP routing into the cluster (like an API Gateway)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;ConfigMap&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Non-sensitive config (env vars)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Secret&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Sensitive config (passwords, API keys)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Namespace&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Logical isolation within a cluster&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Deployment — run 3 replicas of my-app&lt;/span&gt;
&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;apps/v1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Deployment&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;my-app&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;replicas&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;3&lt;/span&gt;
  &lt;span class="na"&gt;selector&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;matchLabels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;my-app&lt;/span&gt;
  &lt;span class="na"&gt;template&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;containers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;my-app&lt;/span&gt;
          &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;myrepo/my-app:1.0&lt;/span&gt;
          &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;containerPort&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;3000&lt;/span&gt;
          &lt;span class="na"&gt;resources&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="na"&gt;requests&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
              &lt;span class="na"&gt;memory&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;128Mi"&lt;/span&gt;
              &lt;span class="na"&gt;cpu&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;250m"&lt;/span&gt;
            &lt;span class="na"&gt;limits&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
              &lt;span class="na"&gt;memory&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;256Mi"&lt;/span&gt;
              &lt;span class="na"&gt;cpu&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;500m"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  K8s Benefits
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Auto-scaling&lt;/strong&gt; — scale pods based on CPU/memory&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Self-healing&lt;/strong&gt; — restart failed containers automatically&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Rolling updates&lt;/strong&gt; — deploy new version with zero downtime&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Load balancing&lt;/strong&gt; — distribute traffic across pods&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Secret management&lt;/strong&gt; — inject secrets as env vars&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Managed K8s Services
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Cloud&lt;/th&gt;
&lt;th&gt;Service&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;AWS&lt;/td&gt;
&lt;td&gt;EKS (Elastic Kubernetes Service)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Google&lt;/td&gt;
&lt;td&gt;GKE (Google Kubernetes Engine)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Azure&lt;/td&gt;
&lt;td&gt;AKS (Azure Kubernetes Service)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  🔄 Container in CI/CD Pipeline
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;Developer pushes code →
&lt;span class="o"&gt;[&lt;/span&gt;GitHub Actions / Jenkins]
  1. Run tests
  2. Build Docker image: docker build &lt;span class="nt"&gt;-t&lt;/span&gt; app:&lt;span class="nv"&gt;$GIT_SHA&lt;/span&gt; &lt;span class="nb"&gt;.&lt;/span&gt;
  3. Push to ECR: docker push app:&lt;span class="nv"&gt;$GIT_SHA&lt;/span&gt;
  4. Deploy to K8s: kubectl &lt;span class="nb"&gt;set &lt;/span&gt;image deployment/app &lt;span class="nv"&gt;app&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;app:&lt;span class="nv"&gt;$GIT_SHA&lt;/span&gt;
  5. K8s rolling update → zero-downtime deploy
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  🎨 Diagram
&lt;/h2&gt;

&lt;p&gt;The diagram shows:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;VM vs Container architecture layers&lt;/li&gt;
&lt;li&gt;Docker image → container lifecycle&lt;/li&gt;
&lt;li&gt;Docker Compose multi-service local setup&lt;/li&gt;
&lt;li&gt;K8s cluster with nodes, pods, services&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  🔑 Key Takeaways
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Containers solve the &lt;strong&gt;environment consistency&lt;/strong&gt; problem — build once, run anywhere&lt;/li&gt;
&lt;li&gt;Always use &lt;strong&gt;multi-stage builds&lt;/strong&gt; to keep images small&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Docker Compose&lt;/strong&gt; for local dev; &lt;strong&gt;Kubernetes&lt;/strong&gt; for production orchestration&lt;/li&gt;
&lt;li&gt;Container images should be &lt;strong&gt;immutable&lt;/strong&gt; — no SSH into containers to fix things; rebuild and redeploy&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>systemdesign</category>
      <category>docker</category>
      <category>kubernetes</category>
      <category>devops</category>
    </item>
    <item>
      <title>Microservices</title>
      <dc:creator>Gouranga Das Samrat</dc:creator>
      <pubDate>Sat, 25 Jul 2026 02:00:00 +0000</pubDate>
      <link>https://dev.to/gouranga-das-khulna/microservices-1gfn</link>
      <guid>https://dev.to/gouranga-das-khulna/microservices-1gfn</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;One-liner:&lt;/strong&gt; Instead of one giant application (monolith), microservices split the system into small, independent services — each owning its own data and deployed separately.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  📌 Monolith vs Microservices
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Monolith
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[One Big App]
 ├── User Module
 ├── Order Module
 ├── Payment Module
 ├── Notification Module
 └── [One Database]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Deploy anything → redeploy everything. One bug → whole app down.&lt;/p&gt;

&lt;h3&gt;
  
  
  Microservices
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[User Service]         → [Users DB]
[Order Service]        → [Orders DB]
[Payment Service]      → [Payments DB]
[Notification Service] → [Notif DB]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each service: independent codebase, independent deployment, independent database.&lt;/p&gt;




&lt;h2&gt;
  
  
  ✅ Pros of Microservices
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Benefit&lt;/th&gt;
&lt;th&gt;Detail&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Independent deployment&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Deploy Payment without touching User Service&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Independent scaling&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Scale only the Order Service during sales&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Tech diversity&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Order Service in Go, ML Service in Python&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Fault isolation&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Notification Service down → Orders still work&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Small teams&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Each team owns one service (Conway's Law)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Easier to understand&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Each service is small and focused&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  ❌ Cons of Microservices
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Problem&lt;/th&gt;
&lt;th&gt;Detail&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Network overhead&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;In-process calls → network calls (latency + failure)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Distributed tracing&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Debugging a request across 10 services is hard&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Data consistency&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;No cross-service ACID transactions&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Operational complexity&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;20 services = 20 deployments, 20 logs, 20 monitors&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Service discovery&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;How does Service A find Service B?&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Testing&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Integration testing is much harder&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  🔄 Inter-Service Communication
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Synchronous (Request-Response)
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Order Service → HTTP/gRPC → Payment Service → response
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;✅ Simple, immediate response&lt;br&gt;&lt;br&gt;
❌ Tight temporal coupling — both must be up&lt;br&gt;&lt;br&gt;
❌ Cascading failures&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;When to use:&lt;/strong&gt; When the caller needs the result immediately (e.g., payment confirmation before showing success page)&lt;/p&gt;
&lt;h3&gt;
  
  
  Asynchronous (Event-Driven)
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Order Service → [Event Bus / Queue] → Payment Service
                                    → Email Service
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;✅ Decoupled — services can be down&lt;br&gt;&lt;br&gt;
✅ Natural fan-out&lt;br&gt;&lt;br&gt;
❌ Eventual consistency&lt;br&gt;&lt;br&gt;
❌ Harder to debug&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;When to use:&lt;/strong&gt; Notifications, analytics, side effects that don't block the user&lt;/p&gt;


&lt;h2&gt;
  
  
  🏗️ Service Communication Patterns
&lt;/h2&gt;
&lt;h3&gt;
  
  
  API Gateway Pattern
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Client → [API Gateway] → routes to appropriate service
                       → handles auth, rate limiting for all
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h3&gt;
  
  
  Service Mesh (Advanced)
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[Service A] ←──[sidecar proxy]──[sidecar proxy]──► [Service B]
                  (Envoy)              (Envoy)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Sidecar handles: mTLS, retries, timeouts, circuit breaking, observability.&lt;br&gt;
Examples: Istio, Linkerd, AWS App Mesh.&lt;/p&gt;
&lt;h3&gt;
  
  
  BFF (Backend for Frontend)
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[Mobile App] → [Mobile BFF] → aggregates calls to services
[Web App]    → [Web BFF]    → different data shape per client
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  🗄️ Database Per Service
&lt;/h2&gt;

&lt;p&gt;Each service &lt;strong&gt;owns its data&lt;/strong&gt;. No shared DB.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;❌ Wrong:
[User Service]  ──┐
[Order Service] ──┼──► [Shared DB]  ← tight coupling at data level
[Payment Service] ┘

✅ Right:
[User Service]    → [User DB (PostgreSQL)]
[Order Service]   → [Order DB (MongoDB)]
[Payment Service] → [Payment DB (PostgreSQL)]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;But what about joins?&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Denormalize data (copy what you need)&lt;/li&gt;
&lt;li&gt;Use API calls to get data from another service&lt;/li&gt;
&lt;li&gt;Use event-driven data replication (each service maintains its own read model)&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  🔒 Saga Pattern — Distributed Transactions
&lt;/h2&gt;

&lt;p&gt;No cross-service ACID transactions. Use &lt;strong&gt;Sagas&lt;/strong&gt; instead.&lt;/p&gt;

&lt;h3&gt;
  
  
  Choreography Saga (Event-Driven)
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Order Created →
  Payment Service listens → charges card → Payment Succeeded event
  Inventory Service listens → reserves stock → Stock Reserved event
  Email Service listens → sends confirmation

If payment fails → Payment Failed event →
  Order Service listens → cancels order
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Orchestration Saga (Central Coordinator)
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[Saga Orchestrator]
  1. → tell Payment Service to charge
  2. → tell Inventory to reserve
  3. → tell Email to send confirmation

If step 2 fails:
  → tell Payment Service to refund (compensating transaction)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  🌡️ Circuit Breaker Pattern
&lt;/h2&gt;

&lt;p&gt;Prevent cascading failures:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Normal:   Service A → Service B (works fine)
Degraded: Service A → Service B (Service B is slow/erroring)
Open:     Service A → [Circuit Open] → return fallback (don't call B)
          Wait 30 seconds → try again (half-open)
          If OK → close circuit → normal again
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;States: &lt;strong&gt;Closed&lt;/strong&gt; (normal) → &lt;strong&gt;Open&lt;/strong&gt; (failing, reject calls) → &lt;strong&gt;Half-Open&lt;/strong&gt; (testing recovery)&lt;/p&gt;

&lt;p&gt;Libraries: Hystrix (Java), resilience4j, Polly (.NET), opossum (Node.js)&lt;/p&gt;




&lt;h2&gt;
  
  
  🔍 Observability in Microservices
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Distributed Tracing
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Request ID: abc-123
→ API Gateway     [10ms]
→ Order Service   [50ms]
→ Payment Service [200ms]  ← slow here!
→ Email Service   [30ms]
Total: 290ms
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Tools: Jaeger, Zipkin, AWS X-Ray, Datadog APM&lt;/p&gt;

&lt;h3&gt;
  
  
  Centralized Logging
&lt;/h3&gt;

&lt;p&gt;All services ship logs to one place: ELK Stack (Elasticsearch + Logstash + Kibana), Datadog, CloudWatch.&lt;/p&gt;

&lt;h3&gt;
  
  
  Metrics &amp;amp; Alerting
&lt;/h3&gt;

&lt;p&gt;Prometheus + Grafana: CPU, memory, request rate, error rate, latency (p50, p95, p99).&lt;/p&gt;




&lt;h2&gt;
  
  
  🎨 Diagram
&lt;/h2&gt;

&lt;p&gt;The diagram shows:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Monolith vs microservices comparison&lt;/li&gt;
&lt;li&gt;API Gateway + service mesh layout&lt;/li&gt;
&lt;li&gt;Saga orchestration flow&lt;/li&gt;
&lt;li&gt;Circuit breaker states&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  🔑 Key Takeaways
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Don't start with microservices&lt;/strong&gt; — start with a well-structured monolith and extract services when you hit scaling/team pain&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Database per service&lt;/strong&gt; is non-negotiable for true independence&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Sagas&lt;/strong&gt; replace distributed transactions (embrace eventual consistency)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Circuit breakers&lt;/strong&gt; are essential — one slow service will take down the whole system without them&lt;/li&gt;
&lt;li&gt;Observability (tracing, logging, metrics) is twice as important in microservices&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>systemdesign</category>
      <category>backend</category>
      <category>microservices</category>
      <category>devops</category>
    </item>
    <item>
      <title>The Go Gateway: Confessions of a Professional Middleman</title>
      <dc:creator>Gouranga Das Samrat</dc:creator>
      <pubDate>Fri, 24 Jul 2026 04:00:00 +0000</pubDate>
      <link>https://dev.to/gouranga-das-khulna/the-go-gateway-confessions-of-a-professional-middleman-ehj</link>
      <guid>https://dev.to/gouranga-das-khulna/the-go-gateway-confessions-of-a-professional-middleman-ehj</guid>
      <description>&lt;p&gt;&lt;em&gt;This is Part 2 of "The Muse of Microservices," a series on the system design behind &lt;a href="https://github.com/GourangaDasSamrat/Polyhymnia" rel="noopener noreferrer"&gt;Polyhymnia&lt;/a&gt;, a deliberately overengineered random quote generator. If you haven't met the whole cast yet, Part 1 covers the architecture end to end — worth reading first.&lt;/em&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Inside Polyhymnia's single HTTP endpoint — orchestration vs choreography, deadline budgets, retry loops, and the art of turning three gRPC calls into one honest JSON response.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  One endpoint, zero excuses
&lt;/h2&gt;

&lt;p&gt;The entire public surface of Polyhymnia is a single route:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="err"&gt;GET /api/quote
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No path parameters, no query strings, no body. And yet everything the system does routes through it, because the Go service in front of it isn't a proxy — it's an &lt;strong&gt;orchestrator&lt;/strong&gt;. It's the one component in the whole stack that knows, and enforces, the correct order of operations: fetch every ID, hand them to the randomizer, fetch the winning quote. Nothing downstream is trusted to figure that sequence out on its own, and nothing downstream is reachable from outside the cluster at all. Every other service in Polyhymnia speaks gRPC exclusively; Go is the only translator standing between "the internet" and "the mesh."&lt;/p&gt;

&lt;p&gt;That's a real, deliberate pattern with a name: the &lt;strong&gt;API gateway&lt;/strong&gt;. Its value isn't glamorous — it's a bulkhead. The public contract (plain HTTP, JSON) is decoupled from the internal contract (gRPC, protobuf), which means the internal services are free to change their wire format, their language, even their topology, without a single line of frontend code ever noticing.&lt;/p&gt;

&lt;h2&gt;
  
  
  Orchestration, chosen on purpose
&lt;/h2&gt;

&lt;p&gt;There are two broad ways to coordinate a multi-step workflow across services. &lt;strong&gt;Choreography&lt;/strong&gt; has each service react to events as they arrive — nobody's in charge, the sequence emerges from a chain of triggers. &lt;strong&gt;Orchestration&lt;/strong&gt; puts one component in the driver's seat, explicitly calling each step and deciding what happens if one fails.&lt;/p&gt;

&lt;p&gt;Polyhymnia picks orchestration, and the gateway's handler reads almost like pseudocode because of it: fetch the ID list, pass it to the randomizer, fetch the chosen quote, encode the response. Three sequential RPCs, one after another, all driven from a single function.&lt;/p&gt;

&lt;p&gt;That choice has real tradeoffs, and Polyhymnia's small enough that you can see both sides clearly:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;You get it:&lt;/strong&gt; a request's entire lifecycle is traceable in one place. Reading the handler top-to-bottom tells you exactly what happened for any given request — there's no event bus to reconstruct, no implicit ordering to reverse-engineer.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;You pay for it:&lt;/strong&gt; the gateway is now coupled to the internal shape of the workflow. If a third backend service joined the pipeline tomorrow, the gateway's code — not some independent event contract — is what has to change.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For a three-step, single-consumer pipeline like this one, orchestration is the right call. It stops being the right call once you have a dozen services and every team wants to own their slice of the sequence independently — which is exactly the kind of judgment call real system design is made of.&lt;/p&gt;

&lt;h2&gt;
  
  
  A deadline budget, not a stopwatch per hop
&lt;/h2&gt;

&lt;p&gt;Here's a detail that's easy to skim past and genuinely worth sitting with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;cancel&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WithTimeout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Context&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;callTimeout&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c"&gt;// 5 seconds&lt;/span&gt;
&lt;span class="k"&gt;defer&lt;/span&gt; &lt;span class="n"&gt;cancel&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That one context is threaded through &lt;em&gt;all three&lt;/em&gt; downstream calls. It is not "5 seconds per RPC" — it's a single 5-second budget for the entire request, shared across &lt;code&gt;GetAllIds&lt;/code&gt;, &lt;code&gt;SelectRandomId&lt;/code&gt;, and &lt;code&gt;GetQuoteById&lt;/code&gt; combined. If the first call to Rust takes 4.5 seconds for whatever reason, the C++ engine and the second Rust call are left splitting the remaining half-second between them, whether they like it or not.&lt;/p&gt;

&lt;p&gt;This is &lt;strong&gt;deadline propagation&lt;/strong&gt;, and it's one of those concepts that looks academic right up until you've been paged for a service that kept retrying a downstream call for 30 seconds because nobody told it the &lt;em&gt;caller&lt;/em&gt; had already given up 29 seconds ago. Polyhymnia's version is about as simple as deadline propagation gets — one shared &lt;code&gt;context.Context&lt;/code&gt;, no per-hop sub-budgets — but the underlying idea, that a deadline should travel &lt;em&gt;with&lt;/em&gt; a request rather than being reinvented at every hop, is exactly the idea production systems lean on.&lt;/p&gt;

&lt;h2&gt;
  
  
  Retrying like you mean it (sort of)
&lt;/h2&gt;

&lt;p&gt;Before the gateway serves a single request, it has to actually connect to both backends:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;maxRetries&lt;/span&gt;    &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="m"&gt;30&lt;/span&gt;
    &lt;span class="n"&gt;retryInterval&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="m"&gt;500&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Millisecond&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;dialGrpc&lt;/code&gt; loops up to 30 times, sleeping 500ms between attempts, using &lt;code&gt;grpc.WithBlock()&lt;/code&gt; so the dial call won't return until a connection actually succeeds (or every attempt is exhausted, at which point the gateway logs a fatal error and exits). That's roughly a 15-second window for the Rust and C++ services to become reachable before the gateway gives up on life entirely.&lt;/p&gt;

&lt;p&gt;It's worth naming precisely what this is and isn't. It's a &lt;strong&gt;fixed-interval retry with a blocking dial&lt;/strong&gt; — simple, predictable, and completely adequate for a small number of startup-order dependencies. It is &lt;em&gt;not&lt;/em&gt; exponential backoff, and it has no jitter. That's a fine tradeoff here: with only two dependencies and a bounded startup window, a fixed 500ms cadence isn't going to hurt anyone. The moment you're retrying against a service under real load with many callers doing the same thing at once, a fixed interval turns into a synchronized herd hammering the same endpoint every 500ms — which is precisely the scenario exponential backoff with jitter exists to prevent. Good instinct to have in your back pocket even when, like here, the simpler tool is the right one for the job.&lt;/p&gt;

&lt;h2&gt;
  
  
  An error taxonomy that actually means something
&lt;/h2&gt;

&lt;p&gt;The gateway's error handling is short, but it draws a distinction a lot of production code gets lazy about:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;idList&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GetIds&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;writeError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;StatusNotFound&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"no quotes available"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;...versus:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;writeError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;StatusBadGateway&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"failed to fetch quote ids"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;An &lt;strong&gt;empty result&lt;/strong&gt; and a &lt;strong&gt;failed call&lt;/strong&gt; are not the same kind of problem, and the gateway refuses to collapse them into one generic "something went wrong" response. A database with zero rows is a legitimate state of the world — the request succeeded, there was simply nothing to return, so it's a &lt;code&gt;404&lt;/code&gt;. A gRPC call that errors out means a dependency is broken or unreachable — that's a &lt;code&gt;502 Bad Gateway&lt;/code&gt;, correctly blaming the infrastructure rather than the caller. A request with the wrong HTTP method gets its own &lt;code&gt;405&lt;/code&gt;. Three distinct failure modes, three distinct status codes, and a frontend that can actually tell them apart if it ever needs to.&lt;/p&gt;

&lt;p&gt;It's a small thing. It's also exactly the kind of small thing that separates an API that's pleasant to build against from one that returns &lt;code&gt;500&lt;/code&gt; for everything and makes every client guess.&lt;/p&gt;

&lt;h2&gt;
  
  
  The localhost trap
&lt;/h2&gt;

&lt;p&gt;Both the gateway's dial addresses and the Rust service's listen address are pinned to explicit IPv4:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="n"&gt;rustDbAddr&lt;/span&gt;    &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"127.0.0.1:50051"&lt;/span&gt;
&lt;span class="n"&gt;cppEngineAddr&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"127.0.0.1:50052"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The code comment explains why, and it's a genuinely useful bit of networking trivia: on some systems, &lt;code&gt;localhost&lt;/code&gt; resolves to &lt;code&gt;127.0.0.1&lt;/code&gt; (IPv4) while a server that bound to &lt;code&gt;[::1]&lt;/code&gt; (IPv6-only) is listening on a &lt;em&gt;different&lt;/em&gt; address entirely. The result is a maddening "connection refused" error against a server you can plainly see is running. Pinning both sides to the same explicit address family sidesteps the ambiguity completely.&lt;/p&gt;

&lt;p&gt;File that one away — it's the kind of bug that costs an afternoon the first time and takes ten seconds to recognize every time after. It'll also come back with a twist in Part 4, once these same services move into separate Docker containers and "127.0.0.1" stops meaning what everyone assumed it meant.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's next
&lt;/h2&gt;

&lt;p&gt;The gateway's job is to ask the right questions in the right order and translate the answers into JSON. It doesn't know how a random ID actually gets picked, and it definitely doesn't know how the database it's querying decided to keep itself safe. Part 3 goes downstream into both of those services — Rust's data-ownership model and C++'s spectacularly unnecessary approach to picking a number — where the real personality of this project lives.&lt;/p&gt;

</description>
      <category>go</category>
      <category>systemdesign</category>
      <category>api</category>
      <category>grpc</category>
    </item>
    <item>
      <title>Meet Polyhymnia: The Random Quote Generator That Took the Scenic Route</title>
      <dc:creator>Gouranga Das Samrat</dc:creator>
      <pubDate>Thu, 23 Jul 2026 04:00:00 +0000</pubDate>
      <link>https://dev.to/gouranga-das-khulna/meet-polyhymnia-the-random-quote-generator-that-took-the-scenic-route-5bg1</link>
      <guid>https://dev.to/gouranga-das-khulna/meet-polyhymnia-the-random-quote-generator-that-took-the-scenic-route-5bg1</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;Why a one-line SQL query became a four-language, five-hop distributed system — and why that's secretly a great way to learn system design.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  The most over-qualified button you'll ever click
&lt;/h2&gt;

&lt;p&gt;Somewhere in this project's proto file lives a service called &lt;code&gt;Randomizer&lt;/code&gt;. Its entire job — its &lt;em&gt;only&lt;/em&gt; job — is to pick one number out of a short list. That's it. That's the service. It has its own gRPC port, its own protobuf messages, and its own opinions about entropy.&lt;/p&gt;

&lt;p&gt;That single fact tells you everything about &lt;strong&gt;Polyhymnia&lt;/strong&gt;: a random quote generator that could have been a &lt;code&gt;SELECT quote, author FROM quotes ORDER BY RANDOM() LIMIT 1;&lt;/code&gt; and one HTML page, and instead is four services, three languages, one build system juggling three different toolchains, and a network hop count that would make a CDN blush.&lt;/p&gt;

&lt;p&gt;This is the first post in a four-part series pulling apart &lt;em&gt;why&lt;/em&gt; it's built this way and &lt;em&gt;what it's quietly teaching you&lt;/em&gt; about real distributed systems along the way. No PhDs required — just a tolerance for services that do less work than the plumbing required to reach them.&lt;/p&gt;

&lt;h2&gt;
  
  
  What it does vs. how it does it
&lt;/h2&gt;

&lt;p&gt;Functionally, Polyhymnia is almost insultingly simple: you click a button, you get a quote and its author. There is no user data, no auth, no pagination, no edge cases worth mentioning.&lt;/p&gt;

&lt;p&gt;Architecturally, that click sets off a small relay race:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;┌────────────┐   HTTP GET    ┌──────────────┐
│  Frontend  │ ────────────▶ │  Go Gateway  │
│ (JS/HTML)  │ ◀──────────── │ (Orchestrator│
└────────────┘   JSON quote  └──────┬───────┘
                                     │
                 ┌───────────────────┼───────────────────┐
                 │ 1. GetAllIds       │ 3. GetQuoteById    │
                 ▼                    │                    ▼
          ┌─────────────┐             │             ┌─────────────┐
          │  Rust "db"  │◀────────────┘             │  Rust "db"  │
          │  service    │                            │  service    │
          └──────┬──────┘                            └─────────────┘
                 │ 2. SelectRandomId
                 ▼
          ┌─────────────┐
          │  C++ engine │
          │ (Randomizer)│
          └─────────────┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Five network hops. Three separate gRPC calls. Two backend services that only exist to serve one HTTP endpoint. The gap between "what this does" and "what it took to do it" is the entire point — and it's a deliberate constraint, not an accident of scope creep.&lt;/p&gt;

&lt;h2&gt;
  
  
  The name is doing a lot of work
&lt;/h2&gt;

&lt;p&gt;Polyhymnia was the muse of sacred poetry and song in Greek mythology — a fitting name for something that ships one-liners. But the real pun is etymological: &lt;em&gt;Poly&lt;/em&gt; (many) + &lt;em&gt;Hymnia&lt;/em&gt; (words/song). Swap "many words" for "many languages," and you get the actual joke — this is a message (a quote) delivered through a genuinely &lt;strong&gt;polyglot&lt;/strong&gt; pipeline: Go, C++, Rust, and JavaScript, four languages for one payload.&lt;/p&gt;

&lt;p&gt;It's a rare case of a project name being both a classics reference and an architecture diagram.&lt;/p&gt;

&lt;h2&gt;
  
  
  The tech stack, with a straight face
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Layer&lt;/th&gt;
&lt;th&gt;Language&lt;/th&gt;
&lt;th&gt;Responsibility&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Frontend&lt;/td&gt;
&lt;td&gt;JavaScript / HTML / CSS&lt;/td&gt;
&lt;td&gt;One button, one quote, zero frameworks&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;API Gateway / Orchestrator&lt;/td&gt;
&lt;td&gt;Go&lt;/td&gt;
&lt;td&gt;HTTP entrypoint, drives the 3-step gRPC workflow&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Mathematical Engine&lt;/td&gt;
&lt;td&gt;C++&lt;/td&gt;
&lt;td&gt;Picks one ID at random, with unnecessary ceremony&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Database Manager &amp;amp; Safety Layer&lt;/td&gt;
&lt;td&gt;Rust&lt;/td&gt;
&lt;td&gt;Owns &lt;code&gt;quotes.db&lt;/code&gt; — the &lt;em&gt;only&lt;/em&gt; service allowed to touch SQLite&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Database&lt;/td&gt;
&lt;td&gt;SQLite&lt;/td&gt;
&lt;td&gt;Three columns: &lt;code&gt;id&lt;/code&gt;, &lt;code&gt;quote&lt;/code&gt;, &lt;code&gt;author&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Inter-service transport&lt;/td&gt;
&lt;td&gt;gRPC + Protobuf&lt;/td&gt;
&lt;td&gt;One shared contract, three independent codegens&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Each row on that table is a genuine, deliberate architectural decision, not a random language pull from a hat. Go fronts the request because it's comfortable being a thin, concurrent I/O layer. Rust owns the only piece of mutable state in the system because "safety layer" is basically Rust's job title. C++ shows up purely to demonstrate that even picking a random array index can be turned into a systems-programming set piece, complete with hand-mixed entropy and pointer arithmetic. We'll get intimate with both of those services in Part 3.&lt;/p&gt;

&lt;h2&gt;
  
  
  Overengineering as a teaching tool, not a punchline
&lt;/h2&gt;

&lt;p&gt;It would be easy to wave this project off as a joke and move on — and it &lt;em&gt;is&lt;/em&gt; a joke, proudly, in the README's own words. But strip away the comedy and what's left underneath is a genuinely well-formed distributed system, built at a scale small enough to actually read in one sitting:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;A single ingress point.&lt;/strong&gt; Only the Go gateway speaks plain HTTP; everything downstream is gRPC-only. That's a real API gateway pattern, not a toy version of one.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Contract-first design.&lt;/strong&gt; One &lt;code&gt;.proto&lt;/code&gt; file is the single source of truth for every message and RPC. Three languages generate their stubs from it independently — nobody hand-writes serialization code.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Strict data ownership.&lt;/strong&gt; Exactly one service is allowed to open the SQLite file. Nothing else touches it, ever, directly or otherwise.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A stateless compute service sitting next to a stateful one.&lt;/strong&gt; The C++ engine holds no data and could be replicated infinitely with zero coordination. The Rust service holds the only state in the system and has to be far more careful about it. That contrast is a genuinely useful thing to internalize, and Polyhymnia hands it to you on a plate.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Orchestration, not choreography.&lt;/strong&gt; The Go gateway explicitly sequences three calls rather than having services react to each other's events. That's a real, debatable system design choice with real tradeoffs — not an accident.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;None of that is complicated in isolation. What makes Polyhymnia worth studying is that it's small enough to hold the &lt;em&gt;entire&lt;/em&gt; system in your head at once, while still containing every major decision point a much bigger distributed system has to make. It's a system design course condensed into something you can &lt;code&gt;git clone&lt;/code&gt; and run on a laptop in a few minutes.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where this series goes from here
&lt;/h2&gt;

&lt;p&gt;Over the next three posts, we're taking the project apart layer by layer:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Part 2&lt;/strong&gt; — the Go gateway: the art of being a professional middleman, and what its retry loop and error-mapping logic quietly teach about deadline budgets and failure taxonomy.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Part 3&lt;/strong&gt; — Rust's safety layer and C++'s beautifully unnecessary randomness: data ownership, parameterized queries, and what "overengineered" entropy actually looks like under the hood.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Part 4&lt;/strong&gt; — shipping the chaos: the shared protobuf contract, three independent codegen pipelines, Docker packaging, and one networking gotcha hiding in plain sight in the compose files.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you want to follow along with the actual code, the repository is here: &lt;strong&gt;&lt;a href="https://github.com/GourangaDasSamrat/Polyhymnia" rel="noopener noreferrer"&gt;GourangaDasSamrat/Polyhymnia&lt;/a&gt;&lt;/strong&gt;. Clone it, click the button a few times, and then come back for Part 2 — where we put the Go gateway under a microscope.&lt;/p&gt;

</description>
      <category>architecture</category>
      <category>systemdesign</category>
      <category>microservices</category>
      <category>grpc</category>
    </item>
    <item>
      <title>Fan-out Architecture</title>
      <dc:creator>Gouranga Das Samrat</dc:creator>
      <pubDate>Sun, 19 Jul 2026 02:00:00 +0000</pubDate>
      <link>https://dev.to/gouranga-das-khulna/fan-out-architecture-el2</link>
      <guid>https://dev.to/gouranga-das-khulna/fan-out-architecture-el2</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;One-liner:&lt;/strong&gt; Fan-out means one event triggers multiple parallel downstream processes — like dropping a pebble in water and watching rings spread outward.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  📌 The Pattern
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[One Event] ──► [Fan-out Layer] ──► [Process A]
                                 ──► [Process B]
                                 ──► [Process C]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;All processes run &lt;strong&gt;simultaneously and independently&lt;/strong&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  🔄 Fan-out on Write vs Fan-out on Read
&lt;/h2&gt;

&lt;p&gt;This distinction is critical for feed/timeline systems (Twitter, Instagram).&lt;/p&gt;

&lt;h3&gt;
  
  
  Fan-out on Write (Push)
&lt;/h3&gt;

&lt;p&gt;When a user posts, immediately write to all followers' timelines:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;User posts → find 1,000 followers → push to each follower's feed cache
Read: O(1) — just read pre-computed feed
Write: O(followers) — expensive for celebrities
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Fan-out on Read (Pull)
&lt;/h3&gt;

&lt;p&gt;When a user opens their feed, compute it on the fly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;User opens feed → fetch followed users → merge their posts → sort
Read: O(following × posts) — slow at scale
Write: O(1) — just save the post
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Hybrid (Best for large systems)
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Normal users → fan-out on write&lt;/li&gt;
&lt;li&gt;Celebrities (&amp;gt;1M followers) → fan-out on read&lt;/li&gt;
&lt;li&gt;Merge both at read time&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  🌍 SNS → SQS Fan-out (AWS)
&lt;/h2&gt;

&lt;p&gt;See &lt;a href="//./pub-sub-sns.md"&gt;Pub-Sub (SNS)&lt;/a&gt; for the full implementation.&lt;/p&gt;

&lt;p&gt;The canonical pattern:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[Event] → [SNS Topic] → [SQS Queue A] → [Consumer A]
                      → [SQS Queue B] → [Consumer B]
                      → [SQS Queue C] → [Consumer C]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;SQS between SNS and consumers adds durability and independent retry per consumer.&lt;/p&gt;

</description>
      <category>aws</category>
      <category>backend</category>
      <category>devops</category>
      <category>systemdesign</category>
    </item>
    <item>
      <title>Pub-Sub (SNS) &amp; Fan-out Architecture</title>
      <dc:creator>Gouranga Das Samrat</dc:creator>
      <pubDate>Sat, 18 Jul 2026 02:00:00 +0000</pubDate>
      <link>https://dev.to/gouranga-das-khulna/pub-sub-sns-fan-out-architecture-14l5</link>
      <guid>https://dev.to/gouranga-das-khulna/pub-sub-sns-fan-out-architecture-14l5</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;One-liner:&lt;/strong&gt; Pub-Sub lets publishers broadcast events to all interested subscribers without knowing who they are. Fan-out ensures one event triggers many parallel actions.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  📌 Pub-Sub Model
&lt;/h2&gt;

&lt;p&gt;In traditional queues: one message → one consumer.&lt;/p&gt;

&lt;p&gt;In Pub-Sub: one message → &lt;strong&gt;all subscribers&lt;/strong&gt; receive a copy.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[Publisher] → [Topic] → [Subscriber A]
                      → [Subscriber B]
                      → [Subscriber C]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  ☁️ AWS SNS (Simple Notification Service)
&lt;/h2&gt;

&lt;p&gt;SNS is AWS's managed Pub-Sub service.&lt;/p&gt;

&lt;h3&gt;
  
  
  Key Concepts
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Term&lt;/th&gt;
&lt;th&gt;Meaning&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Topic&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;A named channel that messages are published to&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Publisher&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Sends messages to a topic&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Subscriber&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Receives copies of messages&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Subscription&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;A subscriber's registration to a topic&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Supported Subscriber Types
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;SQS Queue&lt;/strong&gt; — most common (durable, retry-able)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Lambda&lt;/strong&gt; — serverless processing&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;HTTP/HTTPS endpoint&lt;/strong&gt; — webhooks&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Email / SMS&lt;/strong&gt; — direct notifications&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Mobile Push&lt;/strong&gt; (FCM, APNs) — push notifications&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  🔄 SNS Message Flow
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Order Placed Event
      ↓
[SNS Topic: order-events]
      ├──► [SQS Queue] → [Payment Lambda]
      ├──► [SQS Queue] → [Email Service]
      ├──► [SQS Queue] → [Inventory Service]
      └──► [Lambda]    → [Analytics Ingestion]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;All four happen &lt;strong&gt;simultaneously and independently&lt;/strong&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  🌟 Fan-out Architecture
&lt;/h2&gt;

&lt;p&gt;Fan-out = one event → many parallel processes&lt;/p&gt;

&lt;h3&gt;
  
  
  SNS → SQS Fan-out Pattern (Most Robust)
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[Event Source]
      │
      ▼
[SNS Topic]
   │    │    │
   ▼    ▼    ▼
[SQS] [SQS] [SQS]
  │     │     │
  ▼     ▼     ▼
[Svc A][Svc B][Svc C]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Why SQS in between SNS?&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Durability&lt;/strong&gt; — if Service A is down, message waits in SQS&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Rate control&lt;/strong&gt; — each service processes at its own pace&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Retry&lt;/strong&gt; — SQS handles retries independently per service&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Decoupling&lt;/strong&gt; — SNS doesn't care about downstream state&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Direct SNS Fan-out (No SQS buffer)
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[SNS Topic] → [Lambda A]  (immediate, no buffer)
            → [Lambda B]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Risk: If Lambda B fails, message is lost (no retry queue).&lt;/p&gt;




&lt;h2&gt;
  
  
  🏗️ Real-World Example: User Registration
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;User signs up → [Auth Service] → publishes to SNS: "user.created"
                                          │
                              ┌──────────┼──────────┐
                              ▼          ▼          ▼
                         [SQS]→     [SQS]→     [SQS]→
                         [Welcome   [Analytics  [Referral
                          Email]     Track]      Credit]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each service is independent. If email service is down → message waits → sent later.&lt;br&gt;
Adding a 4th service (e.g., Slack alert)? Just add a new SNS subscription. Zero code change in Auth Service.&lt;/p&gt;


&lt;h2&gt;
  
  
  🆚 SNS vs SQS vs Kafka
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Feature&lt;/th&gt;
&lt;th&gt;SNS&lt;/th&gt;
&lt;th&gt;SQS&lt;/th&gt;
&lt;th&gt;Kafka&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Pattern&lt;/td&gt;
&lt;td&gt;Pub-Sub (broadcast)&lt;/td&gt;
&lt;td&gt;Queue (single consumer)&lt;/td&gt;
&lt;td&gt;Log (many consumers, replay)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Message retention&lt;/td&gt;
&lt;td&gt;None (fire and forget)&lt;/td&gt;
&lt;td&gt;Up to 14 days&lt;/td&gt;
&lt;td&gt;Configurable (forever)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Fan-out&lt;/td&gt;
&lt;td&gt;✅ Native&lt;/td&gt;
&lt;td&gt;❌ (one consumer)&lt;/td&gt;
&lt;td&gt;✅ (consumer groups)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Replay&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Ordering&lt;/td&gt;
&lt;td&gt;Best effort&lt;/td&gt;
&lt;td&gt;FIFO option&lt;/td&gt;
&lt;td&gt;Per-partition&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Durability&lt;/td&gt;
&lt;td&gt;With SQS subscription&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Managed&lt;/td&gt;
&lt;td&gt;✅ AWS&lt;/td&gt;
&lt;td&gt;✅ AWS&lt;/td&gt;
&lt;td&gt;Self-hosted / Confluent&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;


&lt;h2&gt;
  
  
  🔔 SNS for Push Notifications
&lt;/h2&gt;

&lt;p&gt;SNS handles mobile push at scale:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[Your Server] → SNS publishes to platform endpoint
                     ↓
             [FCM] (Android) → User's phone
             [APNs] (iOS) → User's phone
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;SNS manages platform credentials, token management, delivery.&lt;/p&gt;




&lt;h2&gt;
  
  
  📊 Message Filtering (SNS)
&lt;/h2&gt;

&lt;p&gt;Subscribers can filter messages by attributes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;SNS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;message&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;with&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;attributes&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"order_type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"premium"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"region"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"us-east"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"amount"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;999&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;Subscription&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;filter&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;policy&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;(Premium&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;SQS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;only&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;gets&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;premium&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;orders)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"order_type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"premium"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Only premium orders go to the premium processing queue — others are filtered out.&lt;/p&gt;




&lt;h2&gt;
  
  
  🎨 Diagram
&lt;/h2&gt;

&lt;p&gt;The diagram shows:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Publisher → SNS Topic → multiple SQS queues → multiple consumers&lt;/li&gt;
&lt;li&gt;Fan-out flow on user.created event&lt;/li&gt;
&lt;li&gt;Message filtering at subscription level&lt;/li&gt;
&lt;li&gt;DLQ for each consumer independently&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  🔑 Key Takeaways
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Pub-Sub&lt;/strong&gt; (SNS) = broadcast; &lt;strong&gt;Queue&lt;/strong&gt; (SQS) = point-to-point&lt;/li&gt;
&lt;li&gt;Always use &lt;strong&gt;SNS → SQS fan-out&lt;/strong&gt; for durability (SQS buffers failures)&lt;/li&gt;
&lt;li&gt;Pub-Sub enables &lt;strong&gt;zero-code service addition&lt;/strong&gt; — new subscribers need no changes to publisher&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Message filtering&lt;/strong&gt; reduces unnecessary processing across services&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>aws</category>
      <category>systemdesign</category>
      <category>devops</category>
      <category>backend</category>
    </item>
    <item>
      <title>Message Queues (SQS)</title>
      <dc:creator>Gouranga Das Samrat</dc:creator>
      <pubDate>Sun, 12 Jul 2026 02:00:00 +0000</pubDate>
      <link>https://dev.to/gouranga-das-khulna/message-queues-sqs-nj7</link>
      <guid>https://dev.to/gouranga-das-khulna/message-queues-sqs-nj7</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;One-liner:&lt;/strong&gt; A message queue decouples services by letting a producer drop messages into a queue, where consumers pick them up asynchronously — neither needs to be available at the same time.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  📌 The Problem: Tight Coupling
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;User places order →
  [Order Service] → calls [Payment Service] → waits →
                  → calls [Email Service] → waits →
                  → calls [Inventory Service] → waits →
  All done! (3s total, 3 failure points)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Problems:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;High latency&lt;/strong&gt; — user waits for every downstream service&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cascading failure&lt;/strong&gt; — if Email service is down, order fails&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Tight coupling&lt;/strong&gt; — Order service knows about all downstream services&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No retry logic&lt;/strong&gt; — failures are permanent&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  💡 The Solution: Message Queue
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;User places order → [Order Service] → DROP message in queue → RESPOND (fast!)
                                              ↓
                                   [Queue]
                                   /    |    \
                          [Payment] [Email] [Inventory]  ← process at their own pace
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Benefits:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Decoupling&lt;/strong&gt; — services don't know about each other&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Resilience&lt;/strong&gt; — downstream service can be down; message waits in queue&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Load leveling&lt;/strong&gt; — queue absorbs traffic spikes&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Retry&lt;/strong&gt; — failed processing retried automatically&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  🔄 Queue Concepts
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Producer &amp;amp; Consumer
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[Producer] → push message → [Queue] → pull message → [Consumer]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Producer&lt;/strong&gt;: Creates and sends messages&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Consumer&lt;/strong&gt;: Reads and processes messages&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Queue&lt;/strong&gt;: Durable buffer between them&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Message Lifecycle
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;1. Producer sends message → Queue stores it
2. Consumer polls queue → Queue marks message as "in-flight" (invisible to others)
3. Consumer processes → Success: Consumer deletes message
                     → Failure: After visibility timeout, message reappears
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Visibility Timeout
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Consumer pulls message → message hidden for 30 seconds
If consumer crashes → 30 seconds later, message reappears → another consumer picks it up
If consumer succeeds → consumer deletes message → gone forever
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  ☁️ AWS SQS (Simple Queue Service)
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Standard Queue
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Unlimited throughput&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;At-least-once delivery&lt;/strong&gt; — message may be delivered more than once!&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Best-effort ordering&lt;/strong&gt; — no strict FIFO&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  FIFO Queue
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Exactly-once processing&lt;/strong&gt; — deduplication&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Strict ordering&lt;/strong&gt; — first in, first out&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Limited throughput&lt;/strong&gt; — 300 msg/sec (or 3000 with batching)&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  SQS Key Concepts
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Message retention:  Up to 14 days (default: 4 days)
Visibility timeout: How long consumer has to process (default: 30s)
Dead Letter Queue:  Where failed messages go after N retries
Max message size:   256 KB
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  SQS + Lambda (Serverless Consumer)
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[SQS Queue] ──triggers──► [Lambda Function]
                           (auto-scales to process backlog)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  🔀 Queue Patterns
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Work Queue (Task Distribution)
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[Producer] → [Queue] → [Worker 1]
                     → [Worker 2]
                     → [Worker 3]

Each message processed by exactly ONE worker
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Use case: Image resizing, email sending, PDF generation&lt;/p&gt;

&lt;h3&gt;
  
  
  Fan-out via Multiple Queues
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[Event] → [SNS Topic] → [Queue A] → [Consumer A]
                      → [Queue B] → [Consumer B]
                      → [Queue C] → [Consumer C]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each consumer gets a copy of the message. (See: SNS/Pub-Sub)&lt;/p&gt;

&lt;h3&gt;
  
  
  Priority Queue
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;High priority queue → processed first by consumers
Low priority queue  → processed when high is empty
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  ♻️ Dead Letter Queue (DLQ)
&lt;/h2&gt;

&lt;p&gt;When a message fails to process after N retries → move to DLQ:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[Queue] → [Consumer fails 3 times] → [DLQ]
                                          ↑
                                    Ops team inspects,
                                    fixes bug, redrives
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;SQS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;Redrive&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;Policy&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"deadLetterTargetArn"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"arn:aws:sqs:us-east-1:123:my-dlq"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"maxReceiveCount"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;after&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;failures&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;move&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;to&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;DLQ&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  ⚠️ Idempotency — Critical Requirement
&lt;/h2&gt;

&lt;p&gt;Standard queues deliver &lt;strong&gt;at-least-once&lt;/strong&gt;. Your consumer MUST be idempotent:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Message: "charge user 42 for $99"

❌ Non-idempotent: Just run the charge every time → user charged twice!
✅ Idempotent: Check if orderId already processed → skip if yes
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;processPayment&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;orderId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;amount&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="c1"&gt;// Check idempotency key&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;alreadyProcessed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;redis&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`payment:&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;orderId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;alreadyProcessed&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// skip duplicate&lt;/span&gt;

  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;chargeUser&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;redis&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setex&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`payment:&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;orderId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;86400&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;1&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// mark done&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  📊 SQS vs Other Queues
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Feature&lt;/th&gt;
&lt;th&gt;AWS SQS&lt;/th&gt;
&lt;th&gt;RabbitMQ&lt;/th&gt;
&lt;th&gt;Apache Kafka&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Type&lt;/td&gt;
&lt;td&gt;Managed cloud&lt;/td&gt;
&lt;td&gt;Self-hosted broker&lt;/td&gt;
&lt;td&gt;Distributed log&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Retention&lt;/td&gt;
&lt;td&gt;14 days&lt;/td&gt;
&lt;td&gt;Until consumed&lt;/td&gt;
&lt;td&gt;Configurable (forever)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Ordering&lt;/td&gt;
&lt;td&gt;Best-effort/FIFO&lt;/td&gt;
&lt;td&gt;Per-queue&lt;/td&gt;
&lt;td&gt;Per-partition&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Replay&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;✅ (replay from offset)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Throughput&lt;/td&gt;
&lt;td&gt;Very high&lt;/td&gt;
&lt;td&gt;High&lt;/td&gt;
&lt;td&gt;Very high&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Use case&lt;/td&gt;
&lt;td&gt;Decoupling tasks&lt;/td&gt;
&lt;td&gt;Complex routing&lt;/td&gt;
&lt;td&gt;Event streaming&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  🎨 Diagram
&lt;/h2&gt;

&lt;p&gt;The diagram shows:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Producer → Queue → Multiple consumers&lt;/li&gt;
&lt;li&gt;Visibility timeout flow (in-flight → reappear)&lt;/li&gt;
&lt;li&gt;Dead Letter Queue after max retries&lt;/li&gt;
&lt;li&gt;Fan-out via SNS → multiple queues&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  🔑 Key Takeaways
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Queues &lt;strong&gt;decouple&lt;/strong&gt; services and absorb traffic spikes&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Visibility timeout&lt;/strong&gt; ensures messages aren't lost if a consumer crashes&lt;/li&gt;
&lt;li&gt;Always implement &lt;strong&gt;idempotent consumers&lt;/strong&gt; with SQS Standard&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;DLQ&lt;/strong&gt; is essential for debugging failed messages&lt;/li&gt;
&lt;li&gt;Use &lt;strong&gt;FIFO&lt;/strong&gt; when order matters; Standard when throughput matters&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>systemdesign</category>
      <category>devops</category>
      <category>backend</category>
      <category>aws</category>
    </item>
  </channel>
</rss>
