<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Backend-Development on Shane&apos;s Personal Blog</title><description>Recent content in Backend-Development on Shane&apos;s Personal Blog</description><link>https://shanechang.com/tags/backend-development/</link><language>en-us</language><lastBuildDate>Tue, 04 Nov 2025 00:00:00 GMT</lastBuildDate><atom:link href="https://shanechang.com/tags/backend-development/index.xml" rel="self" type="application/rss+xml"/><item><title>What Actually Is ASGI? A Journey from Confusion to Clarity</title><link>https://shanechang.com/p/understanding-asgi-from-confusion-to-clarity/</link><guid isPermaLink="true">https://shanechang.com/p/understanding-asgi-from-confusion-to-clarity/</guid><description>&lt;img src=&quot;https://shanechang.com/_astro/cover.D4v3duLW_217oEx.webp&quot; alt=&quot;Featured image of post What Actually Is ASGI? A Journey from Confusion to Clarity&quot; /&gt;&lt;h2 id=&quot;the-mystery-of-the-invisible-scope&quot;&gt;The Mystery of the Invisible Scope&lt;/h2&gt;
&lt;p&gt;I was building a feature that needed real-time updates. Nothing fancy—just wanted to push some data to clients as things changed on the server. Naturally, I reached for FastAPI and started reading through the documentation. That’s when I first encountered it.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;scope&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;The docs mentioned it everywhere. Tutorial code referenced it. Stack Overflow answers assumed I knew what it was. But here’s the thing that drove me crazy: &lt;strong&gt;I couldn’t find it in any of my actual code&lt;/strong&gt;. It was like everyone was talking about this mysterious variable that just… existed somewhere. Nowhere in my route handlers, nowhere in my WebSocket endpoints. Where was this magical &lt;code&gt;scope&lt;/code&gt; object?&lt;/p&gt;
&lt;p&gt;I felt like I’d walked into the middle of a conversation where everyone understood the context except me.&lt;/p&gt;
&lt;p&gt;Then I started seeing &lt;code&gt;receive&lt;/code&gt; and &lt;code&gt;send&lt;/code&gt; mentioned too. Same problem—referenced everywhere, visible nowhere in the framework code I was writing. AI assistants kept explaining things in terms of these three parameters, but I couldn’t connect the abstract explanations to the concrete code I was looking at in Litestar and Advanced Alchemy documentation.&lt;/p&gt;
&lt;p&gt;That’s when I realized: I’d been learning frameworks without understanding what they were built on. I needed to go deeper—to the bare metal of how Python async web servers actually work. Not the FastAPI way or the Django Channels way, but the fundamental protocol underneath.&lt;/p&gt;
&lt;p&gt;This is the story of that journey. If you’ve ever felt confused about ASGI, WebSockets, or why async Python web stuff feels so different from traditional Flask/Django, this is for you. By the end, you’ll understand not just what ASGI is, but &lt;em&gt;why&lt;/em&gt; it exists and how it creates a unified model for everything from simple HTTP requests to long-lived WebSocket connections.&lt;/p&gt;
&lt;h2 id=&quot;the-first-aha-moment-asgi-is-just-a-contract&quot;&gt;The First Aha Moment: ASGI Is Just a Contract&lt;/h2&gt;
&lt;p&gt;Here’s what finally clicked for me: &lt;strong&gt;ASGI isn’t a framework or a library. It’s a specification—a contract for how web servers talk to Python applications.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Think of it like this: back in the day, everyone agreed on how electrical outlets should work. Any appliance manufacturer could build a device knowing it would fit the standard outlet. ASGI is the same idea, but for async Python web servers and applications.&lt;/p&gt;
&lt;p&gt;The entire contract boils down to three parameters:&lt;/p&gt;
&lt;pre class=&quot;astro-code astro-code-themes github-light-default github-dark-default&quot; style=&quot;background-color:#ffffff;--shiki-dark-bg:#0d1117;color:#1f2328;--shiki-dark:#e6edf3; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;async def app(scope, receive, send):&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    # Your application logic here&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That’s it. That’s the whole interface. Every ASGI application—whether it’s FastAPI, Starlette, Django Channels, or something you build from scratch—is fundamentally just a callable that accepts these three parameters:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;scope&lt;/code&gt;&lt;/strong&gt;: A dictionary containing metadata about the connection. Think of it as the “context” or “session info”—what kind of connection is this? Where’s it coming from? What’s being requested?&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;receive&lt;/code&gt;&lt;/strong&gt;: An async function you call to get messages from the client. Like checking your mailbox for incoming letters.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;send&lt;/code&gt;&lt;/strong&gt;: An async function you call to send messages to the client. Like putting outgoing letters in the mailbox.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The beautiful part? This same simple contract handles everything—regular HTTP requests, streaming Server-Sent Events, bidirectional WebSockets, even application startup and shutdown. The &lt;code&gt;scope&lt;/code&gt; just tells you &lt;em&gt;which kind of communication pattern&lt;/em&gt; you’re dealing with.&lt;/p&gt;
&lt;p&gt;But here’s what confused me at first: what exactly counts as a “session”? Is it one request? A conversation? The lifetime of the server?&lt;/p&gt;
&lt;p&gt;The answer is: &lt;strong&gt;it depends on the scope type&lt;/strong&gt;. And that’s where things get interesting.&lt;/p&gt;
&lt;h2 id=&quot;http-the-simple-transaction&quot;&gt;HTTP: The Simple Transaction&lt;/h2&gt;
&lt;p&gt;Let me start with the familiar: plain old HTTP requests. This is probably what you already understand intuitively, even if you didn’t know ASGI was involved.&lt;/p&gt;
&lt;p&gt;When a client makes an HTTP request to your server, ASGI creates a scope that looks something like this:&lt;/p&gt;
&lt;pre class=&quot;astro-code astro-code-themes github-light-default github-dark-default&quot; style=&quot;background-color:#ffffff;--shiki-dark-bg:#0d1117;color:#1f2328;--shiki-dark:#e6edf3; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    &quot;type&quot;: &quot;http&quot;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    &quot;method&quot;: &quot;GET&quot;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    &quot;path&quot;: &quot;/api/users/123&quot;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    &quot;headers&quot;: [...],&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    &quot;query_string&quot;: b&quot;format=json&quot;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    &quot;client&quot;: (&quot;192.168.1.5&quot;, 54321),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    &quot;server&quot;: (&quot;10.0.0.1&quot;, 8000),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;type: &quot;http&quot;&lt;/code&gt; is the key—it tells your application “this is a simple HTTP request-response transaction.”&lt;/p&gt;
&lt;p&gt;Here’s the flow:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Client sends request → Server creates scope and calls your app&lt;/li&gt;
&lt;li&gt;Your app calls &lt;code&gt;receive()&lt;/code&gt; to get the request body (maybe in chunks)&lt;/li&gt;
&lt;li&gt;Your app calls &lt;code&gt;send()&lt;/code&gt; with response.start (status code, headers)&lt;/li&gt;
&lt;li&gt;Your app calls &lt;code&gt;send()&lt;/code&gt; again with response.body (the actual content)&lt;/li&gt;
&lt;li&gt;Scope ends. Connection done.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Think of it like a vending machine: you insert money (request), press a button (the route), and get a snack back (response). Transaction complete. The machine doesn’t remember you existed.&lt;/p&gt;
&lt;p&gt;That “doesn’t remember” part is crucial—HTTP in ASGI is &lt;strong&gt;stateless&lt;/strong&gt;. Each request is independent. The scope lives for maybe 100 milliseconds, just long enough to handle one request-response cycle.&lt;/p&gt;
&lt;p&gt;Example messages you’d send:&lt;/p&gt;
&lt;pre class=&quot;astro-code astro-code-themes github-light-default github-dark-default&quot; style=&quot;background-color:#ffffff;--shiki-dark-bg:#0d1117;color:#1f2328;--shiki-dark:#e6edf3; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;# First, send the response headers and status&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;await send({&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    &quot;type&quot;: &quot;http.response.start&quot;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    &quot;status&quot;: 200,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    &quot;headers&quot;: [[b&quot;content-type&quot;, b&quot;application/json&quot;]],&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;})&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;# Then send the actual response body&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;await send({&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    &quot;type&quot;: &quot;http.response.body&quot;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    &quot;body&quot;: b&apos;{&quot;user&quot;: &quot;Shane&quot;, &quot;id&quot;: 123}&apos;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;})&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Simple, stateless, short-lived. This is the foundation—the easiest pattern to understand.&lt;/p&gt;
&lt;h2 id=&quot;server-sent-events-the-one-way-stream&quot;&gt;Server-Sent Events: The One-Way Stream&lt;/h2&gt;
&lt;p&gt;Now, what if your vending machine needed to keep telling you about new snacks as they were restocked? You’d want to stay connected and receive updates, but you wouldn’t be sending anything back except “yes, keep the updates coming.”&lt;/p&gt;
&lt;p&gt;That’s Server-Sent Events (SSE).&lt;/p&gt;
&lt;p&gt;Here’s what confused me initially: SSE uses an HTTP scope, but it doesn’t follow the typical request-response pattern. Instead:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Client makes an HTTP request&lt;/li&gt;
&lt;li&gt;Server responds with headers (including &lt;code&gt;Content-Type: text/event-stream&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Connection stays open&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Server keeps sending chunks of data whenever it wants&lt;/li&gt;
&lt;li&gt;Eventually, either side closes the connection&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The scope is still &lt;code&gt;type: &quot;http&quot;&lt;/code&gt;, but the interaction is different. It’s like calling a restaurant for their daily specials, and instead of hanging up after they tell you today’s menu, they keep you on the line and tell you every time a new special is added.&lt;/p&gt;
&lt;p&gt;Example of what the server might send:&lt;/p&gt;
&lt;pre class=&quot;astro-code astro-code-themes github-light-default github-dark-default&quot; style=&quot;background-color:#ffffff;--shiki-dark-bg:#0d1117;color:#1f2328;--shiki-dark:#e6edf3; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;await send({&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    &quot;type&quot;: &quot;http.response.start&quot;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    &quot;status&quot;: 200,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    &quot;headers&quot;: [[b&quot;content-type&quot;, b&quot;text/event-stream&quot;]],&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;})&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;# Then keep sending updates...&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;await send({&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    &quot;type&quot;: &quot;http.response.body&quot;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    &quot;body&quot;: b&quot;data: {\&quot;new_order\&quot;: 42}\n\n&quot;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    &quot;more_body&quot;: True,  # Signal that more data is coming&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;})&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;# ... later ...&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;await send({&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    &quot;type&quot;: &quot;http.response.body&quot;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    &quot;body&quot;: b&quot;data: {\&quot;new_order\&quot;: 43}\n\n&quot;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    &quot;more_body&quot;: True,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;})&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Notice &lt;code&gt;more_body: True&lt;/code&gt;? That’s the key. It tells ASGI “don’t close the connection, I’ve got more to send.”&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;SSE is still fundamentally HTTP&lt;/strong&gt;—it’s just long-lived HTTP. The client doesn’t send data back through this connection (it’s one-way), but the connection can stay open for minutes or even hours.&lt;/p&gt;
&lt;p&gt;When to use SSE:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Live dashboards showing real-time metrics&lt;/li&gt;
&lt;li&gt;Notification feeds&lt;/li&gt;
&lt;li&gt;Stock tickers&lt;/li&gt;
&lt;li&gt;Progress updates for long-running tasks&lt;/li&gt;
&lt;li&gt;Any scenario where the server pushes updates but doesn’t need responses&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;It’s simpler than WebSockets because it’s unidirectional, but more powerful than regular HTTP because it’s persistent.&lt;/p&gt;
&lt;h2 id=&quot;websocket-the-full-conversation&quot;&gt;WebSocket: The Full Conversation&lt;/h2&gt;
&lt;p&gt;Then I hit WebSockets. And this is where my understanding of ASGI really got tested.&lt;/p&gt;
&lt;p&gt;WebSockets are fundamentally different from both HTTP and SSE because they’re &lt;strong&gt;bidirectional and stateful&lt;/strong&gt;. This isn’t a transaction or a one-way stream—it’s a ongoing conversation where both sides can speak whenever they want.&lt;/p&gt;
&lt;p&gt;If HTTP is like sending letters and SSE is like listening to a radio broadcast, &lt;strong&gt;WebSocket is like a phone call&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;The scope for WebSocket looks different:&lt;/p&gt;
&lt;pre class=&quot;astro-code astro-code-themes github-light-default github-dark-default&quot; style=&quot;background-color:#ffffff;--shiki-dark-bg:#0d1117;color:#1f2328;--shiki-dark:#e6edf3; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    &quot;type&quot;: &quot;websocket&quot;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    &quot;path&quot;: &quot;/ws/chat/room-42&quot;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    &quot;headers&quot;: [...],&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    &quot;query_string&quot;: b&quot;user=shane&quot;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    &quot;client&quot;: (&quot;192.168.1.5&quot;, 54322),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    &quot;server&quot;: (&quot;10.0.0.1&quot;, 8000),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Notice: &lt;code&gt;type: &quot;websocket&quot;&lt;/code&gt;. This signals a completely different kind of interaction.&lt;/p&gt;
&lt;p&gt;WebSocket messages include:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Incoming (from client to your app):&lt;/strong&gt;&lt;/p&gt;
&lt;pre class=&quot;astro-code astro-code-themes github-light-default github-dark-default&quot; style=&quot;background-color:#ffffff;--shiki-dark-bg:#0d1117;color:#1f2328;--shiki-dark:#e6edf3; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;{&quot;type&quot;: &quot;websocket.connect&quot;}       # Client wants to start a session&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;{&quot;type&quot;: &quot;websocket.receive&quot;, &quot;text&quot;: &quot;Hello!&quot;}  # Client sent a message&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;{&quot;type&quot;: &quot;websocket.disconnect&quot;}    # Client ended the session&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Outgoing (from your app to client):&lt;/strong&gt;&lt;/p&gt;
&lt;pre class=&quot;astro-code astro-code-themes github-light-default github-dark-default&quot; style=&quot;background-color:#ffffff;--shiki-dark-bg:#0d1117;color:#1f2328;--shiki-dark:#e6edf3; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;{&quot;type&quot;: &quot;websocket.accept&quot;}        # You approve the connection&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;{&quot;type&quot;: &quot;websocket.send&quot;, &quot;text&quot;: &quot;Welcome!&quot;}   # You send a message&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;{&quot;type&quot;: &quot;websocket.close&quot;}         # You end the session&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;See the difference? With HTTP, you receive a request and send a response. With WebSocket, you’re dealing with a &lt;strong&gt;state machine&lt;/strong&gt;: connect → accept → ongoing send/receive loop → close.&lt;/p&gt;
&lt;p&gt;The scope lives for the entire WebSocket session—could be seconds, could be hours. You might exchange hundreds of messages within a single scope. This is fundamentally stateful—the server remembers this connection exists and can push data to it anytime.&lt;/p&gt;
&lt;p&gt;When to use WebSocket:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Real-time chat applications&lt;/li&gt;
&lt;li&gt;Collaborative editing (like Google Docs)&lt;/li&gt;
&lt;li&gt;Multiplayer games&lt;/li&gt;
&lt;li&gt;Live video/audio streaming control&lt;/li&gt;
&lt;li&gt;Any scenario requiring true bidirectional real-time communication&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;But here’s what really confused me when I first learned this…&lt;/p&gt;
&lt;h2 id=&quot;the-mystery-of-the-websocket-handshake&quot;&gt;The Mystery of the WebSocket Handshake&lt;/h2&gt;
&lt;p&gt;I kept seeing references to “WebSocket upgrade” and “HTTP to WebSocket upgrade handshake.” And I had this burning question: &lt;strong&gt;if WebSocket is different from HTTP, how does it even start?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Here’s the part that blew my mind: &lt;strong&gt;WebSockets always begin as HTTP requests&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Wait, what?&lt;/p&gt;
&lt;p&gt;Let me explain. WebSockets were designed to work with existing web infrastructure—the same ports, the same proxies, the same SSL/TLS setup. So instead of inventing an entirely new protocol from scratch, they built it on top of HTTP.&lt;/p&gt;
&lt;p&gt;Here’s what actually happens when a browser wants to open a WebSocket connection:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Step 1: Client sends a special HTTP request&lt;/strong&gt;&lt;/p&gt;
&lt;pre class=&quot;astro-code astro-code-themes github-light-default github-dark-default&quot; style=&quot;background-color:#ffffff;--shiki-dark-bg:#0d1117;color:#1f2328;--shiki-dark:#e6edf3; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;GET /ws/chat HTTP/1.1&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Host: example.com&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Upgrade: websocket&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Connection: Upgrade&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Sec-WebSocket-Key: X3JJHMbDL1EzLkh9GBhXDw==&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Sec-WebSocket-Version: 13&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This is a regular HTTP GET request, but with special headers saying “hey, I’d like to upgrade this connection to WebSocket.”&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Step 2: Server responds with HTTP 101&lt;/strong&gt;&lt;/p&gt;
&lt;pre class=&quot;astro-code astro-code-themes github-light-default github-dark-default&quot; style=&quot;background-color:#ffffff;--shiki-dark-bg:#0d1117;color:#1f2328;--shiki-dark:#e6edf3; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;HTTP/1.1 101 Switching Protocols&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Upgrade: websocket&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Connection: Upgrade&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Sec-WebSocket-Accept: &amp;#x3C;computed response&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That &lt;code&gt;101 Switching Protocols&lt;/code&gt; status is the magic handshake. It means: “Okay, we’re no longer speaking HTTP. From this moment forward, this TCP connection will use WebSocket protocol instead.”&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Step 3: Protocol switch happens&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;The underlying TCP connection stays open, but HTTP is done. Now both sides start speaking WebSocket framing protocol—a completely different language.&lt;/p&gt;
&lt;p&gt;Think of it like this: You call a restaurant (HTTP), ask to be transferred to a specific table (upgrade request), the host says “transferring you now” (101 response), and suddenly you’re having a direct conversation with someone at that table (WebSocket). Same phone line, different conversation protocol.&lt;/p&gt;
&lt;h2 id=&quot;what-happens-at-the-asgi-layer&quot;&gt;What Happens at the ASGI Layer?&lt;/h2&gt;
&lt;p&gt;Here’s what confused me the most: &lt;strong&gt;where does ASGI fit into this handshake?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;The answer: ASGI comes in &lt;em&gt;after&lt;/em&gt; the handshake is complete.&lt;/p&gt;
&lt;p&gt;Let me break down what happens at different layers:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Server Level (Uvicorn/Hypercorn—automatic, not your code):&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Receives the HTTP upgrade request&lt;/li&gt;
&lt;li&gt;Validates WebSocket headers&lt;/li&gt;
&lt;li&gt;Sends back the 101 Switching Protocols response&lt;/li&gt;
&lt;li&gt;Switches the TCP connection to WebSocket framing&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;ASGI Level (your application code):&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Server creates a WebSocket scope&lt;/li&gt;
&lt;li&gt;Server sends you a &lt;code&gt;websocket.connect&lt;/code&gt; message&lt;/li&gt;
&lt;li&gt;Now YOU decide: accept or reject?&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This two-layer decision-making finally made sense when I understood it this way:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Protocol layer (server):&lt;/strong&gt; “Is this a valid WebSocket upgrade request?”&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Application layer (your code):&lt;/strong&gt; “Do I &lt;em&gt;want&lt;/em&gt; to allow this specific connection?” (authentication, authorization, rate limiting, etc.)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The handshake is already done by the time your application sees &lt;code&gt;websocket.connect&lt;/code&gt;. The client is already “connected” at the protocol level. Your &lt;code&gt;websocket.accept&lt;/code&gt; isn’t completing the handshake—it’s your application’s business logic saying “yes, I approve this session.”&lt;/p&gt;
&lt;p&gt;Example flow in your ASGI app:&lt;/p&gt;
&lt;pre class=&quot;astro-code astro-code-themes github-light-default github-dark-default&quot; style=&quot;background-color:#ffffff;--shiki-dark-bg:#0d1117;color:#1f2328;--shiki-dark:#e6edf3; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;# You receive this from the server&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;message = await receive()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;# message = {&quot;type&quot;: &quot;websocket.connect&quot;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;# Now you decide - maybe check authentication&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;if user_is_authenticated:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    await send({&quot;type&quot;: &quot;websocket.accept&quot;})&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    # Now the bidirectional message loop can begin&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;else:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    await send({&quot;type&quot;: &quot;websocket.close&quot;, &quot;code&quot;: 1008})&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    # Reject the connection - unauthorized&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This distinction between server-level (protocol) and application-level (business logic) was my biggest “aha!” moment. The framework documentation suddenly made sense—FastAPI’s WebSocket endpoints, Starlette’s connection handling, all of it was operating at the application layer, trusting the server (Uvicorn) to handle the protocol layer.&lt;/p&gt;
&lt;p&gt;Either side can close the connection at any time. There’s no “client is in control” or “server is in control”—it’s a true peer-to-peer conversation (well, as peer-to-peer as client-server can be). When either side sends a close message, the WebSocket session ends and the scope completes.&lt;/p&gt;
&lt;h2 id=&quot;lifespan-the-orthogonal-dimension&quot;&gt;Lifespan: The Orthogonal Dimension&lt;/h2&gt;
&lt;p&gt;Just when I thought I understood the pattern—HTTP, SSE, and WebSocket representing different communication patterns—I encountered a fourth scope type that didn’t fit the model at all.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;lifespan&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Here’s what tripped me up: I initially thought “lifespan” meant “the lifespan of a connection.” Like, the duration from when a WebSocket connects until it disconnects. That made intuitive sense!&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;I was completely wrong.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Lifespan has nothing to do with individual requests or connections. It’s about &lt;strong&gt;the application itself&lt;/strong&gt;—the entire server process from startup to shutdown.&lt;/p&gt;
&lt;p&gt;Think of it this way:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;HTTP scope: exists for one request (~100ms)&lt;/li&gt;
&lt;li&gt;SSE scope: exists for one streaming session (minutes to hours)&lt;/li&gt;
&lt;li&gt;WebSocket scope: exists for one bidirectional conversation (seconds to hours)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Lifespan scope: exists for the entire application (days to months)&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;When your server starts up, before it handles any HTTP requests or WebSocket connections, it creates a lifespan scope and sends you startup messages. When the server is shutting down (gracefully), it sends you shutdown messages.&lt;/p&gt;
&lt;p&gt;The scope looks like:&lt;/p&gt;
&lt;pre class=&quot;astro-code astro-code-themes github-light-default github-dark-default&quot; style=&quot;background-color:#ffffff;--shiki-dark-bg:#0d1117;color:#1f2328;--shiki-dark:#e6edf3; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    &quot;type&quot;: &quot;lifespan&quot;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The messages you receive:&lt;/p&gt;
&lt;pre class=&quot;astro-code astro-code-themes github-light-default github-dark-default&quot; style=&quot;background-color:#ffffff;--shiki-dark-bg:#0d1117;color:#1f2328;--shiki-dark:#e6edf3; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;{&quot;type&quot;: &quot;lifespan.startup&quot;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;{&quot;type&quot;: &quot;lifespan.shutdown&quot;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;What would you use this for? Things that need to happen once per application, not per request:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Opening database connection pools&lt;/li&gt;
&lt;li&gt;Loading machine learning models into memory&lt;/li&gt;
&lt;li&gt;Starting background task schedulers&lt;/li&gt;
&lt;li&gt;Warming caches&lt;/li&gt;
&lt;li&gt;Setting up monitoring/metrics collectors&lt;/li&gt;
&lt;li&gt;Graceful cleanup on shutdown&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Analogy time: If HTTP/SSE/WebSocket are like serving individual customers in a restaurant, lifespan is like opening the restaurant in the morning (turning on ovens, prepping ingredients) and closing it at night (cleaning up, shutting down equipment).&lt;/p&gt;
&lt;p&gt;You don’t want to load a 2GB ML model on every HTTP request—you load it once during &lt;code&gt;lifespan.startup&lt;/code&gt; and reuse it for all requests. You don’t want to abruptly kill database connections—you close them gracefully during &lt;code&gt;lifespan.shutdown&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;In FastAPI, this is what those &lt;code&gt;@app.on_event(&quot;startup&quot;)&lt;/code&gt; and &lt;code&gt;@app.on_event(&quot;shutdown&quot;)&lt;/code&gt; decorators are doing—they’re handling lifespan messages for you.&lt;/p&gt;
&lt;p&gt;Lifespan is orthogonal to the other three scope types. It’s not about communication patterns between client and server—it’s about the lifecycle of the server process itself.&lt;/p&gt;
&lt;h2 id=&quot;the-unified-mental-model&quot;&gt;The Unified Mental Model&lt;/h2&gt;
&lt;p&gt;After all this confusion, trial, error, and eventual clarity, here’s the mental model that finally made ASGI click for me:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;ASGI is a universal interface for describing “communication sessions” between servers and applications, where a session can be:&lt;/strong&gt;&lt;/p&gt;



































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Scope Type&lt;/th&gt;&lt;th&gt;Duration&lt;/th&gt;&lt;th&gt;Direction&lt;/th&gt;&lt;th&gt;Use Case&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;HTTP&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;Milliseconds&lt;/td&gt;&lt;td&gt;Request → Response&lt;/td&gt;&lt;td&gt;Simple API calls, page loads, form submissions&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;SSE&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;Minutes to hours&lt;/td&gt;&lt;td&gt;Server → Client (one-way)&lt;/td&gt;&lt;td&gt;Live feeds, dashboards, notifications&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;WebSocket&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;Seconds to hours&lt;/td&gt;&lt;td&gt;Bidirectional&lt;/td&gt;&lt;td&gt;Chat, collaboration, games, real-time updates&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Lifespan&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;Application lifetime&lt;/td&gt;&lt;td&gt;N/A&lt;/td&gt;&lt;td&gt;Startup/shutdown tasks, resource management&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;All four use the exact same interface: &lt;code&gt;async def app(scope, receive, send)&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;scope&lt;/code&gt; dictionary tells you what kind of session you’re dealing with. The &lt;code&gt;receive&lt;/code&gt; and &lt;code&gt;send&lt;/code&gt; functions let you interact with that session. The same contract, different behaviors.&lt;/p&gt;
&lt;p&gt;What makes this elegant is that &lt;strong&gt;middleware can work across all types&lt;/strong&gt;. Want to add authentication? Write middleware that checks the scope type and handles HTTP, WebSocket, and SSE appropriately. Want to add logging? Same deal—one interface, universal application.&lt;/p&gt;
&lt;p&gt;This is why frameworks like FastAPI feel so natural once you understand ASGI. Under the hood, every route handler, every WebSocket endpoint, every startup event—they’re all just ASGI applications conforming to this same simple contract.&lt;/p&gt;
&lt;p&gt;The mystery of the invisible &lt;code&gt;scope&lt;/code&gt; finally made sense. It’s there in every ASGI application—frameworks just abstract it away so you don’t have to think about it for simple cases. But when you need to go deeper, when you need to understand &lt;em&gt;why&lt;/em&gt; WebSockets work differently from HTTP or &lt;em&gt;where&lt;/em&gt; to put your database initialization, understanding the bare ASGI layer gives you that clarity.&lt;/p&gt;
&lt;h2 id=&quot;from-confusion-to-clarity&quot;&gt;From Confusion to Clarity&lt;/h2&gt;
&lt;p&gt;When I started this journey, &lt;code&gt;scope&lt;/code&gt; was an invisible, mysterious thing that documentation assumed I understood. Now I see it for what it is: a simple dictionary describing a communication pattern.&lt;/p&gt;
&lt;p&gt;The beautiful part is how this knowledge transfers. Now when I read FastAPI docs and see WebSocket examples, I understand what’s happening beneath the decorator syntax. When I see Starlette’s startup events, I know they’re handling lifespan messages. When I troubleshoot why a connection isn’t staying open, I can reason about whether it’s an HTTP scope that ended naturally or a WebSocket that closed unexpectedly.&lt;/p&gt;
&lt;p&gt;Going to the bare metal—understanding the actual protocol instead of just the framework—transformed my confusion into confidence.&lt;/p&gt;
&lt;p&gt;If you’re building real-time features, async APIs, or just trying to understand modern Python web development, I hope this journey helps demystify ASGI for you the way it did for me. Next time you see that mysterious &lt;code&gt;scope&lt;/code&gt; parameter in the documentation, you’ll know exactly what it means.&lt;/p&gt;
&lt;p&gt;And maybe, just maybe, you’ll find yourself diving even deeper—implementing a minimal ASGI app from scratch, writing custom middleware, or truly understanding what your framework is doing behind the scenes.&lt;/p&gt;
&lt;p&gt;The rabbit hole goes deeper if you want it to. But at least now you know where the entrance is.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;em&gt;(Written by Human, improved using AI where applicable.)&lt;/em&gt;&lt;/p&gt;</description><pubDate>Tue, 04 Nov 2025 00:00:00 GMT</pubDate></item><item><title>The 12-Factor App: Building Software Like LEGO in the Cloud Era</title><link>https://shanechang.com/p/twelve-factor-app-lego-cloud-era/</link><guid isPermaLink="true">https://shanechang.com/p/twelve-factor-app-lego-cloud-era/</guid><description>&lt;img src=&quot;https://shanechang.com/_astro/cover.B0hq-9yw_Z1Q0puh.webp&quot; alt=&quot;Featured image of post The 12-Factor App: Building Software Like LEGO in the Cloud Era&quot; /&gt;&lt;h2 id=&quot;intro&quot;&gt;Intro&lt;/h2&gt;
&lt;p&gt;Picture this: It’s 3 AM, and your app just went viral. Traffic spikes from 100 users to 100,000 in minutes. Half your team is asleep, the other half is panicking. Will your application gracefully scale to handle the load, or will it crumble under pressure?&lt;/p&gt;
&lt;p&gt;As a startup, this scenario haunted me as I build our application—until I read something that fundamentally changed how I think about building applications. It’s called the 12-factor app methodology (knowledge and wisdoms discovered like this always humbles me—we are, after all, standing on the shoulders of giants, and hoping to see further into the future)&lt;/p&gt;
&lt;p&gt;Here’s how I think about this: &lt;strong&gt;A 12-factor app is like a LEGO brick for infrastructure&lt;/strong&gt;. You build it with zero assumptions about the outside world, purely implementing internal logic while letting the platform decide everything els—what inputs to feed it, where outputs go, when to start, stop, duplicate, or destroy it. The app becomes not just a one-off creation but an infinitely scalable building block (like lego for the cloud).&lt;/p&gt;
&lt;h2 id=&quot;the-three-pillars-understanding-the-architecture&quot;&gt;The Three Pillars: Understanding the Architecture&lt;/h2&gt;
&lt;p&gt;Before diving into the twelve factors, let me share the mental model that finally made everything click. Every interaction in a 12-factor app falls into one of three categories:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;The App Itself&lt;/strong&gt; - The immutable, stateless core of your logic&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;The Interface&lt;/strong&gt; - How your app communicates with the outside world&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;The Platform&lt;/strong&gt; - The environment that orchestrates everything&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Think of it like this: Your app is a chef in a kitchen. The chef (the app) has specific skills and recipes. The kitchen setup—where ingredients come from, where dishes go, what equipment is available—that’s the interface. The restaurant management deciding when the chef works, how many chefs to hire, which orders to prioritize—that’s the platform.&lt;/p&gt;
&lt;p&gt;This separation is what makes the magic happen. Let’s explore how each factor reinforces this architecture.&lt;/p&gt;
&lt;h2 id=&quot;part-1-the-foundation---managing-your-code-and-dependencies&quot;&gt;Part 1: The Foundation - Managing Your Code and Dependencies&lt;/h2&gt;
&lt;h3 id=&quot;factor-i-codebase---your-single-source-of-truth&quot;&gt;Factor I: Codebase - Your Single Source of Truth&lt;/h3&gt;
&lt;p&gt;Remember the last time you heard “it works on my machine” or spent hours figuring out which version of the code was actually running in production? The codebase principle eliminates these nightmares.&lt;/p&gt;
&lt;p&gt;Imagine your codebase as a map where every piece of code has exact coordinates—the X-axis represents different versions, the Y-axis represents different deployments. Any snapshot of your code can be pinpointed precisely on this map. When something breaks at 3 AM, you know &lt;em&gt;exactly&lt;/em&gt; which code is running where.&lt;/p&gt;
&lt;p&gt;This has not be a problem personally—since I adopted github blindly (shame on me) during college, I always put my code there (I naively thought of it as a cloud for my code. I can just pull code from anywhere—fun!) but stupid me: its way more complex than that, which I learned as I use github with teammates. The only way you truly understand how a version control navigates landmines is to actually use it wrong (which i did lol) and get hit on the head with a brick. Then you learn.&lt;/p&gt;
&lt;h3 id=&quot;factors-ii--iii-dependencies-and-config---the-input-controls&quot;&gt;Factors II &amp;#x26; III: Dependencies and Config - The Input Controls&lt;/h3&gt;
&lt;p&gt;These two factors are cousins—they both control how the outside world shapes your app’s behavior, but in subtly different ways.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Dependencies&lt;/strong&gt; are like the ingredients list for a recipe. You declare exactly what you need (Python packages, Node modules, system libraries) in a manifest file. No assumptions, no “it should probably have this installed.” Every dependency is explicit.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Configuration&lt;/strong&gt;, on the other hand, is like the knobs on a mixing board. Same equipment, different settings for different venues. Your database credentials, API keys, feature flags—these change between environments, but your code doesn’t.&lt;/p&gt;
&lt;p&gt;Here’s the crucial insight: Your app’s config is everything that varies between deploys. If you can’t open-source your code right now without leaking credentials, you haven’t separated config from code properly.&lt;/p&gt;
&lt;p&gt;for python, what I like to do with settings is just use dotenv + dataclass. Reliable, barebone yet powerful.&lt;/p&gt;
&lt;h3 id=&quot;factor-iv-backing-services---your-apps-supporting-cast&quot;&gt;Factor IV: Backing Services - Your App’s Supporting Cast&lt;/h3&gt;
&lt;p&gt;Backing services are where input and output blur together. Your database, message queue, email service—they’re both sources of input (what data do I read?) and destinations for output (where do I write?).&lt;/p&gt;
&lt;p&gt;The breakthrough principle here: &lt;strong&gt;treat every backing service as an attached resource&lt;/strong&gt;. Your app shouldn’t care whether PostgreSQL is running on the same machine, in a different datacenter, or managed by a third party. It’s just a URL and credentials in your config.&lt;/p&gt;
&lt;p&gt;For example, this really shines when you need to migrate from a self-hosted PostgreSQL to Amazon RDS—if you treat the database as an attached resource, the migration is simply config change. (the problem is never about coding, the problem is always about the bug that comes with new codes. LOL)&lt;/p&gt;
&lt;h2 id=&quot;part-2-the-build-pipeline---from-code-to-running-process&quot;&gt;Part 2: The Build Pipeline - From Code to Running Process&lt;/h2&gt;
&lt;h3 id=&quot;factor-v-build-release-run---the-three-stage-rocket&quot;&gt;Factor V: Build, Release, Run - The Three-Stage Rocket&lt;/h3&gt;
&lt;p&gt;Lets talk about bread. 4AM in the morning, You bake it (Build), then you got a bunch of breads at 9am when you open (releases), then you sell the breads during the day (run). Sure if you are a small bakery you can live with baking fresh breads and sell them right after (mix build and run) but if you bake the bread wrong, your customer would need to wait an extra hour for a new batch. It would be so much better if you bake early and have a bunch of ready bread and satisified customers!&lt;/p&gt;
&lt;p&gt;Now that you have a rough understanding what the three stages are, lets dive deeper:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Build Stage&lt;/strong&gt;: Your code + dependencies become an executable (compile, bundle, package)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Release Stage&lt;/strong&gt;: That executable + configuration becomes a numbered release&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Run Stage&lt;/strong&gt;: That release executes in the production environment&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Each release is immutable—a frozen snapshot combining a specific build with specific configuration. When release v427 starts acting weird, you can instantly roll back to v426. You’re not scrambling to figure out what changed; you’re switching between known, tested states.&lt;/p&gt;
&lt;p&gt;The beauty? Problems in the build stage happen when developers are awake and watching. Problems in the run stage can be solved by rolling back to a known-good release. No more debugging in production at 3 AM.&lt;/p&gt;
&lt;h3 id=&quot;factor-vi-processes---the-stateless-mandate&quot;&gt;Factor VI: Processes - The Stateless Mandate&lt;/h3&gt;
&lt;p&gt;Your app processes should be like workers on an assembly line—they process what comes to them but don’t store anything locally. Need to save data? Use a database. Need to cache something? Use Redis. Need to store files? Use S3.&lt;/p&gt;
&lt;p&gt;When your processes are stateless, they become disposable. You can start them, stop them, crash them, multiply them—and your users never notice. It’s liberating.&lt;/p&gt;
&lt;h3 id=&quot;factor-vii-port-binding---self-contained-services&quot;&gt;Factor VII: Port Binding - Self-Contained Services&lt;/h3&gt;
&lt;p&gt;Your app should be completely self-contained, exporting its services by binding to a port. No runtime injection of web servers, no complex application containers—just your app listening on a port.&lt;/p&gt;
&lt;p&gt;This means your Python app includes its own web server (like Gunicorn), your Ruby app brings Thin or Puma, your Java app packages Jetty. The app becomes a standalone service that says, “I’m listening on port 5000. Send me requests.” (This is why RESTful is so elegant—it is a standard that unify the world!)&lt;/p&gt;
&lt;p&gt;Why does this matter? Because it makes your app composable. Today’s web app becomes tomorrow’s backing service for another app. Just point to its URL and port. It’s LEGO bricks all the way down.&lt;/p&gt;
&lt;h2 id=&quot;part-3-operations---running-in-the-wild&quot;&gt;Part 3: Operations - Running in the Wild&lt;/h2&gt;
&lt;h3 id=&quot;factor-viii-concurrency---scale-out-not-up&quot;&gt;Factor VIII: Concurrency - Scale Out, Not Up&lt;/h3&gt;
&lt;p&gt;Typically when building local apps, the instinct is to make your app handle more concurrent request—add threads, increase the connection pool, optimize the event loop. They make things faster. But for web applications that needs to scale, the 12-factor way says: don’t.&lt;/p&gt;
&lt;p&gt;Instead, keep your app simple—handle requests one at a time if that’s natural. Let the platform handle concurrency by running multiple copies of your process. Need to handle 10x traffic? Run 10x processes. It’s crude, but it works brilliantly.&lt;/p&gt;
&lt;p&gt;Think of it like a restaurant. The traditional approach is training your one chef to cook faster, juggle more pans, multitask frantically. The 12-factor approach? Hire more chefs. Each chef works at a sustainable pace, and you scale by adding chefs, not by working them harder.&lt;/p&gt;
&lt;h3 id=&quot;factor-ix-disposability---fast-startup-graceful-shutdown&quot;&gt;Factor IX: Disposability - Fast Startup, Graceful Shutdown&lt;/h3&gt;
&lt;p&gt;Your processes should be like Phoenix—ready to die and be reborn at a moment’s notice. Fast startup—meaning lazy approach, and graceful shutdown (finish current work, then exit cleanly).&lt;/p&gt;
&lt;p&gt;This disposability enables the magic of modern deployment. Rolling updates, automatic scaling, self-healing systems—they all depend on processes that can be created and destroyed without drama.&lt;/p&gt;
&lt;h3 id=&quot;factor-x-devprod-parity---closing-the-gap&quot;&gt;Factor X: Dev/Prod Parity - Closing the Gap&lt;/h3&gt;
&lt;p&gt;The traditional gaps:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Time gap&lt;/strong&gt;: Code written Monday, deployed next month&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Personnel gap&lt;/strong&gt;: Developers write, ops team deploys&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Tools gap&lt;/strong&gt;: SQLite in dev, PostgreSQL in production&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The 12-factor approach obliterates these gaps:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Deploy hours or minutes after writing code&lt;/li&gt;
&lt;li&gt;Developers are involved in deployment&lt;/li&gt;
&lt;li&gt;Use the same backing services everywhere&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;We all know why mutex in multi-threading is important: it prevents race conditions where unpredictable things could happen when state is volatile. The same logic applies here: you want the gap to be as small and controlled as possible when you are between volatile / sensitive states so that you always have a preditive result, rather than chaos.&lt;/p&gt;
&lt;h2 id=&quot;part-4-observability---logs-and-admin-tasks&quot;&gt;Part 4: Observability - Logs and Admin Tasks&lt;/h2&gt;
&lt;h3 id=&quot;factor-xi-logs---the-event-stream&quot;&gt;Factor XI: Logs - The Event Stream&lt;/h3&gt;
&lt;p&gt;Your app should write logs like a diary writer who doesn’t care who reads it—just stream consciousness to stdout. Don’t manage log files, don’t rotate logs, don’t even think about where they go. Just write to stdout and let the platform handle the rest.&lt;/p&gt;
&lt;p&gt;This seemed weird until I saw its power. In development, logs appear in your terminal. In production, the platform can route them anywhere—to files, to Elasticsearch, to DataDog, to multiple destinations simultaneously. Your app doesn’t know or care.&lt;/p&gt;
&lt;p&gt;It’s the same philosophy: the app does one thing (emit events), the platform handles the complexity (routing, storage, analysis).&lt;/p&gt;
&lt;h3 id=&quot;factor-xii-admin-processes---one-off-tasks-in-familiar-territory&quot;&gt;Factor XII: Admin Processes - One-Off Tasks in Familiar Territory&lt;/h3&gt;
&lt;p&gt;Database migrations, console sessions, data fix scripts—these admin processes should run in the same environment as your regular app processes. Same code, same config, same dependencies.&lt;/p&gt;
&lt;p&gt;Why? Because nothing is worse than a migration script that works perfectly in staging but fails in production because of some subtle environmental difference. When admin processes run in identical environments, you eliminate an entire category of “works on my machine” problems.&lt;/p&gt;
&lt;h2 id=&quot;the-revelation-its-all-about-contracts&quot;&gt;The Revelation: It’s All About Contracts&lt;/h2&gt;
&lt;p&gt;The 12-factor methodology is never about the twelve specific factors. It’s about defining a clean contract between your application and the platform it runs on.&lt;/p&gt;
&lt;p&gt;Your app promises:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;To be stateless and disposable&lt;/li&gt;
&lt;li&gt;To declare its needs explicitly&lt;/li&gt;
&lt;li&gt;To communicate through standard interfaces&lt;/li&gt;
&lt;li&gt;To log to stdout&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The platform promises:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;To provide configuration&lt;/li&gt;
&lt;li&gt;To manage processes&lt;/li&gt;
&lt;li&gt;To route requests&lt;/li&gt;
&lt;li&gt;To handle logs&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;With this contract in place, something magical happens. Your app becomes truly portable. It can run on Heroku today, Kubernetes tomorrow, and whatever comes next. It can scale from one user to millions without code changes. It becomes a true building block—a LEGO brick that clicks perfectly into any modern infrastructure.&lt;/p&gt;</description><pubDate>Tue, 19 Aug 2025 00:00:00 GMT</pubDate></item></channel></rss>