<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Software Architecture on Shane&apos;s Personal Blog</title><description>Recent content in Software Architecture on Shane&apos;s Personal Blog</description><link>https://shanechang.com/tags/software-architecture/</link><language>en-us</language><lastBuildDate>Fri, 28 Nov 2025 00:00:00 GMT</lastBuildDate><atom:link href="https://shanechang.com/tags/software-architecture/index.xml" rel="self" type="application/rss+xml"/><item><title>The Two-Agent Method: Building Features Without Drowning in Context</title><link>https://shanechang.com/p/two-agent-method-building-features-complex-codebase/</link><guid isPermaLink="true">https://shanechang.com/p/two-agent-method-building-features-complex-codebase/</guid><description>&lt;img src=&quot;https://shanechang.com/_astro/cover.BYQFEibf_1GaBcE.webp&quot; alt=&quot;Featured image of post The Two-Agent Method: Building Features Without Drowning in Context&quot; /&gt;&lt;p&gt;There’s a particular kind of frustration that comes from working with AI coding assistants on anything beyond trivial features. You start with a clear goal. The AI seems to understand. It explores your codebase, asks a few questions, maybe even proposes something sensible. Then, somewhere in the implementation, things drift. The code doesn’t quite integrate properly. Edge cases get missed. The assistant confidently suggests changes that would break existing functionality.&lt;/p&gt;
&lt;p&gt;You find yourself debugging the assistant’s confusion rather than shipping features.&lt;/p&gt;
&lt;p&gt;The problem isn’t that these tools are useless - they’re remarkably capable when used correctly. The problem is that most of us are using them wrong. We’re treating them like patient junior developers who can hold an entire conversation in their head, when they’re actually more like brilliant consultants with severe short-term memory constraints.&lt;/p&gt;
&lt;p&gt;Once you understand this, a better approach becomes obvious.&lt;/p&gt;
&lt;h2 id=&quot;the-real-problem-context-accumulation&quot;&gt;The Real Problem: Context Accumulation&lt;/h2&gt;
&lt;p&gt;When you have a long conversation with an AI assistant, every detour leaves a mark. Every wrong file it explored, every assumption it made and corrected, every dead-end investigation - all of it stays in the conversation history. The model doesn’t forget these things the way a human might. Instead, it accumulates them.&lt;/p&gt;
&lt;p&gt;Think of it like trying to focus on writing while someone reads aloud every draft you’ve ever written, including all your false starts and deleted paragraphs. The signal-to-noise ratio degrades steadily.&lt;/p&gt;
&lt;p&gt;This is why implementations often deteriorate after seemingly productive planning sessions. The assistant reached a correct conclusion, but only after wandering through a forest of incorrect ones. When it tries to implement, it’s carrying all that baggage.&lt;/p&gt;
&lt;p&gt;The solution isn’t to use these tools less. It’s to structure your workflow around their actual capabilities.&lt;/p&gt;
&lt;h2 id=&quot;the-two-agent-pattern&quot;&gt;The Two-Agent Pattern&lt;/h2&gt;
&lt;p&gt;Here’s the approach that consistently produces better results: split the work between two separate agents with distinct, focused roles.&lt;/p&gt;
&lt;p&gt;The first agent - call it the Explorer - does exactly what the name suggests. It navigates your codebase, asks clarifying questions, maps out integration points, and produces a clean implementation plan. This agent’s job is to understand deeply and document clearly.&lt;/p&gt;
&lt;p&gt;The second agent - the Implementer - starts fresh. It receives the Explorer’s distilled insights without any of the exploration baggage. Clean instructions, relevant context, no detours. This agent writes the actual code.&lt;/p&gt;
&lt;p&gt;The pattern resembles how effective engineering teams actually work. Senior engineers who understand the system architecture don’t necessarily write every line of code. They explore, they plan, they document their findings. Then someone else - or themselves with fresh focus - implements based on that clear blueprint.&lt;/p&gt;
&lt;h2 id=&quot;phase-one-exploration-with-constraints&quot;&gt;Phase One: Exploration With Constraints&lt;/h2&gt;
&lt;p&gt;Start by framing the problem clearly. This matters more than you might think. The quality of the final implementation depends heavily on how well you define the task upfront.&lt;/p&gt;
&lt;p&gt;Give the Explorer autonomy to navigate your codebase, but constrain its exploration to relevant areas. If you’re adding a feature to your authentication system, point it toward the auth modules explicitly. This prevents the assistant from wandering through your entire repository and burning context window on irrelevant code.&lt;/p&gt;
&lt;p&gt;The instruction might look like: “Explore the authentication and user management modules. I need to add OAuth integration for Google and Microsoft. Read whatever files you think are relevant in these areas.”&lt;/p&gt;
&lt;p&gt;Then add something critical: “If you’re not completely confident about the solution, ask questions. Don’t make assumptions.”&lt;/p&gt;
&lt;p&gt;This single instruction changes the assistant’s behavior dramatically. Most models will happily fill gaps with assumptions if you don’t explicitly tell them otherwise. But when forced to acknowledge uncertainty, they ask surprisingly good questions.&lt;/p&gt;
&lt;p&gt;Answer these questions thoroughly. The time you invest here determines how much backtracking you’ll do later. If the assistant asks whether soft-deleted users should still authenticate, don’t just say “no” - explain the entire deletion lifecycle and how it affects related systems.&lt;/p&gt;
&lt;p&gt;The Explorer continues iterating - reading more code, asking follow-up questions, refining its understanding. Eventually, it reaches confidence: “I understand what needs to be built. Here’s the plan.”&lt;/p&gt;
&lt;h2 id=&quot;the-critical-transition&quot;&gt;The Critical Transition&lt;/h2&gt;
&lt;p&gt;This is where most people make their mistake. They see a good plan and immediately say “great, implement it” - keeping the same conversation going.&lt;/p&gt;
&lt;p&gt;Don’t do this.&lt;/p&gt;
&lt;p&gt;Instead, instruct the Explorer to write documentation for a completely different engineer. Not instructions - documentation. The difference matters.&lt;/p&gt;
&lt;p&gt;Instructions sound like: “Add a deleted_at field to the User model. Modify UserService.get_active_users() to filter out deleted users.”&lt;/p&gt;
&lt;p&gt;Documentation sounds like: “The User model in models/user.py represents both active and deleted users. There’s currently no soft-delete mechanism - the existing delete() method removes records entirely. For this feature, you’ll likely want to add a nullable deleted_at timestamp. The UserService in services/user_service.py has methods that return user collections - these may need updating to respect the deletion state. The admin interface renders user lists from admin/users.py, which pulls from UserService.”&lt;/p&gt;
&lt;p&gt;See the difference? Documentation guides thinking. It highlights relevant files and suggests approaches without dictating implementation details. It preserves the Implementer’s ability to think independently.&lt;/p&gt;
&lt;p&gt;Have the Explorer include:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Relevant file paths and what they contain&lt;/li&gt;
&lt;li&gt;Integration points and why they matter&lt;/li&gt;
&lt;li&gt;Potential approaches and their tradeoffs&lt;/li&gt;
&lt;li&gt;Your specific requirements and constraints&lt;/li&gt;
&lt;li&gt;Your coding style preferences&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This becomes your handoff document.&lt;/p&gt;
&lt;h2 id=&quot;phase-two-fresh-implementation&quot;&gt;Phase Two: Fresh Implementation&lt;/h2&gt;
&lt;p&gt;Start a new conversation. Copy in the Explorer’s handoff document. Add your original feature request for context.&lt;/p&gt;
&lt;p&gt;Then watch something interesting happen.&lt;/p&gt;
&lt;p&gt;If the Explorer did its job well, the Implementer will read through the suggested files, understand the architecture quickly, and say: “I’m ready to implement this.”&lt;/p&gt;
&lt;p&gt;This moment is actually a quality signal. When an Implementer can immediately start working from a handoff document, it suggests the documentation was clear and complete. When it needs extensive clarification, something was missing in the exploration phase.&lt;/p&gt;
&lt;p&gt;The Implementer now builds the feature with several advantages:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Clean context focused only on relevant information&lt;/li&gt;
&lt;li&gt;No cognitive debris from exploration detours&lt;/li&gt;
&lt;li&gt;Fresh perspective on integration challenges&lt;/li&gt;
&lt;li&gt;Full autonomy to make implementation decisions&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The resulting code tends to be cleaner, more consistent with existing patterns, and better integrated with the broader system.&lt;/p&gt;
&lt;h2 id=&quot;why-this-actually-works&quot;&gt;Why This Actually Works&lt;/h2&gt;
&lt;p&gt;Large language models don’t “forget” in the human sense - they process every token in their context window with roughly equal weight. A confused exploration thirty messages ago carries nearly the same influence as the current task description.&lt;/p&gt;
&lt;p&gt;By resetting context, you’re not just saving tokens. You’re removing misleading signals that would otherwise contaminate the implementation.&lt;/p&gt;
&lt;p&gt;Think of it like the difference between:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;A meeting where you discuss something for two hours, exploring every wrong answer before finding the right one, then trying to make decisions while mentally replaying the entire rambling conversation&lt;/li&gt;
&lt;li&gt;A meeting where someone walks in with a clear one-page summary of the decision and the relevant context&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The second meeting produces better outcomes because everyone’s working from filtered, relevant information.&lt;/p&gt;
&lt;h2 id=&quot;a-concrete-example&quot;&gt;A Concrete Example&lt;/h2&gt;
&lt;p&gt;Suppose you’re adding export functionality to a data platform. Your existing system has complex permission rules, multiple data formats, and several edge cases around sensitive information.&lt;/p&gt;
&lt;p&gt;The Explorer would:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Examine your permission system to understand access control patterns&lt;/li&gt;
&lt;li&gt;Review existing export code (if any) to maintain consistency&lt;/li&gt;
&lt;li&gt;Identify where sensitive data filtering happens&lt;/li&gt;
&lt;li&gt;Ask about specific requirements: file format preferences, size limits, async vs sync processing&lt;/li&gt;
&lt;li&gt;Investigate how similar features handle errors and logging&lt;/li&gt;
&lt;li&gt;Produce a plan that covers data pipeline, permission checks, format conversion, and error handling&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The handoff document might note: “The existing report generation in services/reports.py follows an async pattern using Celery tasks. This might be worth considering for exports since large datasets could timeout on synchronous requests. Permission checks happen at the service layer, not the API layer - see how ReportService.get_report() handles this. The data sanitization utilities in utils/sanitize.py are used consistently across the codebase for removing sensitive fields.”&lt;/p&gt;
&lt;p&gt;The Implementer reads this, examines the relevant files, and builds an export feature that naturally fits your architecture. It uses Celery because that’s your pattern. It checks permissions at the service layer because that’s where you do it. It uses your existing sanitization utilities because those exist.&lt;/p&gt;
&lt;p&gt;The alternative - having a single agent do everything - often results in exports that don’t quite match your patterns. Maybe it checks permissions at the wrong layer. Maybe it uses synchronous processing when you’ve standardized on async. These aren’t catastrophic failures, but they create the kind of inconsistency that makes codebases harder to maintain.&lt;/p&gt;
&lt;h2 id=&quot;when-to-use-this-pattern&quot;&gt;When to Use This Pattern&lt;/h2&gt;
&lt;p&gt;Not every task needs this ceremony. Adding a simple utility function? Fixing an obvious bug? A single agent works fine.&lt;/p&gt;
&lt;p&gt;This pattern pays dividends when:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The feature touches multiple parts of your system&lt;/li&gt;
&lt;li&gt;Integration matters more than the individual pieces&lt;/li&gt;
&lt;li&gt;You need the new code to feel native to the existing codebase&lt;/li&gt;
&lt;li&gt;The problem requires real understanding of your architecture&lt;/li&gt;
&lt;li&gt;You’ve found yourself repeatedly explaining context to the assistant&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;As a rough heuristic: if you’d want a senior engineer to review the design before implementation in a human team, use two agents.&lt;/p&gt;
&lt;h2 id=&quot;common-variations&quot;&gt;Common Variations&lt;/h2&gt;
&lt;p&gt;Some people use this pattern with even more specialization:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;An Architect agent that only reviews designs&lt;/li&gt;
&lt;li&gt;A Reviewer agent that critiques implementations&lt;/li&gt;
&lt;li&gt;A Documenter agent that writes end-user documentation&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The underlying principle remains the same: keep each agent’s context clean and focused on its specific role.&lt;/p&gt;
&lt;h2 id=&quot;the-hidden-benefit&quot;&gt;The Hidden Benefit&lt;/h2&gt;
&lt;p&gt;Beyond the obvious quality improvements, this pattern has a subtler advantage: it forces you to think clearly about what you’re building.&lt;/p&gt;
&lt;p&gt;Writing a good handoff document requires understanding the feature yourself. When the Explorer asks clarifying questions, you can’t hand-wave - you have to provide real answers. The transition point becomes a natural checkpoint for “do we actually know what we’re building?”&lt;/p&gt;
&lt;p&gt;Many bugs get prevented not because the AI wrote better code, but because this process caught ambiguities before they became implementation.&lt;/p&gt;
&lt;h2 id=&quot;practical-notes&quot;&gt;Practical Notes&lt;/h2&gt;
&lt;p&gt;A few details that matter in practice:&lt;/p&gt;
&lt;p&gt;Start the Explorer’s instructions with your problem framing and relevant directory suggestions. Don’t make it search your entire repository.&lt;/p&gt;
&lt;p&gt;When the Explorer asks questions, take them seriously even if they seem obvious. Sometimes obvious questions expose subtle inconsistencies in requirements.&lt;/p&gt;
&lt;p&gt;The handoff document doesn’t need to be long. Three or four paragraphs often suffice. The goal is clarity, not completeness.&lt;/p&gt;
&lt;p&gt;If the Implementer asks extensive clarification questions about the handoff, that’s feedback - the Explorer missed something. Consider refining the exploration process.&lt;/p&gt;
&lt;p&gt;Your coding style preferences (formatting, documentation standards, naming conventions) should be in the handoff. The Implementer won’t inherit them through osmosis.&lt;/p&gt;
&lt;h2 id=&quot;limitations&quot;&gt;Limitations&lt;/h2&gt;
&lt;p&gt;This approach doesn’t solve everything. The Implementer can still write bugs. Integration might surface unexpected issues. Code reviews remain necessary.&lt;/p&gt;
&lt;p&gt;What it does solve is the context contamination problem - and that alone makes complex features dramatically more reliable to build with AI assistance.&lt;/p&gt;
&lt;h2 id=&quot;the-broader-pattern&quot;&gt;The Broader Pattern&lt;/h2&gt;
&lt;p&gt;Once you see this pattern, you’ll notice it applies beyond coding. Any complex task with an AI assistant benefits from similar structure: a research phase that produces clean documentation, followed by an execution phase that works from that documentation.&lt;/p&gt;
&lt;p&gt;The specific implementation varies by domain, but the principle holds: cognitive clarity beats raw capability. A moderately capable model working from clean context often outperforms a more powerful model drowning in conversational debris.&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>Fri, 28 Nov 2025 00:00:00 GMT</pubDate></item><item><title>Why AI Cannot Engineer</title><link>https://shanechang.com/p/why-ai-cannot-engineer/</link><guid isPermaLink="true">https://shanechang.com/p/why-ai-cannot-engineer/</guid><description>&lt;img src=&quot;https://shanechang.com/_astro/cover.CTct8cc3_1GfOkr.webp&quot; alt=&quot;Featured image of post Why AI Cannot Engineer&quot; /&gt;&lt;p&gt;I asked Claude to build me a customer dashboard last month. Ten minutes later, I had gorgeous React components, a clean API structure, and authentication that worked flawlessly on my laptop. I felt like a wizard.&lt;/p&gt;
&lt;p&gt;Two days later, when we put it in front of actual users, everything fell apart. The API crumbled under concurrent requests. The authentication tokens expired in the middle of user sessions. The database queries that felt snappy with my 10 test records took 30 seconds with real data.&lt;/p&gt;
&lt;p&gt;The code looked beautiful. It even worked—sort of. But it was a disaster waiting to happen, and I had no one to blame but myself. I’d asked for a construction worker when I needed an architect.&lt;/p&gt;
&lt;h2 id=&quot;the-skyscraper-nobody-can-live-in&quot;&gt;The Skyscraper Nobody Can Live In&lt;/h2&gt;
&lt;p&gt;Think about building a skyscraper for a moment. You need two completely different types of expertise:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The Architects and Engineers&lt;/strong&gt; design the building. They figure out where the load-bearing walls go, how to route all the electrical wiring and plumbing, what materials can handle the wind shear at that height, how the building responds to earthquakes. They think about fire escapes and elevator shafts and how people will actually move through the space. They create detailed specifications for everything.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The Construction Workers&lt;/strong&gt; build it. They know how to mix concrete, how to weld steel beams, how to install windows. Give them a good blueprint with all the specifications—“use this gauge of rebar, space them 6 inches apart, let the concrete cure for 48 hours”—and they’ll build you something solid and safe.&lt;/p&gt;
&lt;p&gt;Here’s the interesting part: construction workers understand building principles. They know that rebar makes concrete stronger, that you can’t just drill holes wherever you want in a load-bearing wall, that certain materials expand in heat. They might not be able to calculate the exact compression loads or explain the physics, but they know &lt;em&gt;how&lt;/em&gt; to build.&lt;/p&gt;
&lt;p&gt;So in theory, if you asked construction workers to build a skyscraper without giving them architectural plans, they could do it. They’d use their knowledge to put up walls and install wiring and plumbing. The building would stand.&lt;/p&gt;
&lt;p&gt;For a while.&lt;/p&gt;
&lt;p&gt;Then someone tries to actually &lt;em&gt;live&lt;/em&gt; in it. Or an earthquake hits. Or there’s a fire. And you realize the electrical system is a tangled mess that can’t handle the load. The plumbing creates massive pressure problems on the upper floors. There are no proper fire exits. The structure can’t flex with the wind. It looks like a building, but it’s not safe, not livable, not reliable. It’s a expensive disaster wrapped in a pretty facade.&lt;/p&gt;
&lt;p&gt;You really needed those architects and engineers.&lt;/p&gt;
&lt;h2 id=&quot;we-accidentally-merged-two-jobs&quot;&gt;We Accidentally Merged Two Jobs&lt;/h2&gt;
&lt;p&gt;Now let’s talk about software. In our industry, we have the same two roles—but we’ve gotten confused about which is which.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Software Engineers&lt;/strong&gt; are supposed to design systems. They think about scalability, reliability, security, maintainability. They establish patterns and guardrails. They consider edge cases: What happens when 10,000 users hit this endpoint simultaneously? How do we handle partial failures? What’s our data consistency model? How do we make this debuggable when something goes wrong at 3am? They create the “blueprints” that make the difference between software that works on localhost and software that works in production.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Programmers&lt;/strong&gt; write code. They understand syntax, algorithms, design patterns. They know how to implement features, how to make things work. Given good specifications and architectural guidelines, they write solid, maintainable code that does exactly what it needs to do.&lt;/p&gt;
&lt;p&gt;But here’s where we got confused: engineers also write code. They have to—you can’t design a system well if you don’t understand implementation realities. An architect who doesn’t understand how concrete and steel actually behave can’t design a building that stands. Similarly, a software engineer needs to understand how code actually executes.&lt;/p&gt;
&lt;p&gt;Because of this overlap, we started treating them as the same job. Most companies just have “software engineers” who do everything—system design &lt;em&gt;and&lt;/em&gt; implementation. We stopped distinguishing between the person who decides “we need a caching layer with these invalidation rules” and the person who implements Redis with that specification.&lt;/p&gt;
&lt;p&gt;For years, this worked okay. The engineer-programmer would design the system in their head, then implement it. Both roles were happening, just inside one person.&lt;/p&gt;
&lt;h2 id=&quot;enter-the-ai-programmer&quot;&gt;Enter the AI Programmer&lt;/h2&gt;
&lt;p&gt;Now we have large language models that can write code. And here’s what I’ve realized: &lt;strong&gt;LLMs are incredible programmers, but terrible engineers.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;An LLM is like that construction worker who can build you a skyscraper without blueprints. Ask it to “build me a todo list app,” and it will. The code will be clean. It’ll have proper separation of concerns. The variable names will make sense. It’ll work beautifully on your laptop.&lt;/p&gt;
&lt;p&gt;But it’s missing all the engineering:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;No load considerations:&lt;/strong&gt; It’ll use an in-memory array for the todo list, which works great for testing but fails when you have 10,000 users&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;No failure handling:&lt;/strong&gt; What happens when the database connection drops mid-request? The LLM’s code probably doesn’t handle that gracefully&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;No security thinking:&lt;/strong&gt; Authentication might be present, but is it actually secure against common attacks? Are there SQL injection vulnerabilities?&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;No performance engineering:&lt;/strong&gt; That nested loop through every user’s todos on every request? Didn’t consider that case&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;No operational thinking:&lt;/strong&gt; Good luck debugging when something goes wrong—there’s minimal logging and no monitoring&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The code &lt;em&gt;looks&lt;/em&gt; professional. It follows good practices. It even works, in the ideal case. But the moment reality hits—concurrent users, network failures, edge cases, malicious input—it falls apart.&lt;/p&gt;
&lt;p&gt;Just like that skyscraper built without architects.&lt;/p&gt;
&lt;h2 id=&quot;what-this-actually-means&quot;&gt;What This Actually Means&lt;/h2&gt;
&lt;p&gt;I used to think the distinction between “engineer” and “programmer” was just semantic nitpicking. Now I realize it’s crucial, and we’re about to learn this lesson the hard way.&lt;/p&gt;
&lt;p&gt;When you tell an LLM “build me an authentication system,” you’re giving it a task without constraints. It’s like telling a construction worker “build me a building” without specifying:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;How many people it needs to support&lt;/li&gt;
&lt;li&gt;What kind of disasters it needs to withstand&lt;/li&gt;
&lt;li&gt;What the fire safety requirements are&lt;/li&gt;
&lt;li&gt;How the utilities should be routed&lt;/li&gt;
&lt;li&gt;What the budget constraints are&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The LLM will build you &lt;em&gt;something&lt;/em&gt;. That something might even impress you. But it won’t be robust, because nobody did the engineering.&lt;/p&gt;
&lt;p&gt;Here’s a real example from my startup, Empath Legal. We’re building jury selection tools for attorneys. I could ask an LLM: “Create an API endpoint that analyzes jury questionnaires and returns insights.”&lt;/p&gt;
&lt;p&gt;The LLM would build me something functional:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;It would parse the questionnaires&lt;/li&gt;
&lt;li&gt;It would call an LLM to analyze the text&lt;/li&gt;
&lt;li&gt;It would return formatted results&lt;/li&gt;
&lt;li&gt;The code would be clean and well-structured&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;But here’s what it wouldn’t consider without engineering input:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Business requirements I need to specify:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;These questionnaires can be 50+ pages and attorneys need results in under 30 seconds&lt;/li&gt;
&lt;li&gt;Our customers are lawyers who need citations for everything—we can’t just hallucinate insights&lt;/li&gt;
&lt;li&gt;The analysis must be deterministic enough that an attorney can rely on it in court&lt;/li&gt;
&lt;li&gt;We have a credit-based pricing model, so we need to track and limit usage&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Technical guardrails I need to establish:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;We need to chunk large questionnaires intelligently (by section, not arbitrary token counts)&lt;/li&gt;
&lt;li&gt;We must implement streaming so attorneys see progressive results, not a 30-second blank screen&lt;/li&gt;
&lt;li&gt;We need a caching layer so re-analyzing the same questionnaire doesn’t burn credits&lt;/li&gt;
&lt;li&gt;We need robust error handling because LLM API calls fail sometimes&lt;/li&gt;
&lt;li&gt;We need audit trails for compliance reasons&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;System design decisions I need to make:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Should this be synchronous or async? (Async, because of the 30-second requirement)&lt;/li&gt;
&lt;li&gt;What happens if the LLM call fails midway? (We need idempotent retries)&lt;/li&gt;
&lt;li&gt;How do we handle rate limits from the LLM provider? (We need a queue with backpressure)&lt;/li&gt;
&lt;li&gt;What’s our data consistency model? (We need to decide what happens if analysis partially completes)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The LLM could write beautiful code for the happy path. But without me doing the engineering work—understanding the requirements, establishing the constraints, designing the system architecture—that code would be worthless in production.&lt;/p&gt;
&lt;h2 id=&quot;the-new-division-of-labor&quot;&gt;The New Division of Labor&lt;/h2&gt;
&lt;p&gt;This isn’t about LLMs being bad. They’re phenomenal at what they do. This is about understanding what they do.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;LLMs are expert programmers:&lt;/strong&gt; Give them a well-specified task with clear constraints, and they’ll implement it beautifully. They know patterns, they write clean code, they can even debug and optimize within a defined scope.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Humans need to be the engineers:&lt;/strong&gt; We need to understand the business requirements, design the system architecture, establish the guardrails and principles, consider the edge cases and failure modes. We need to ask: What are we really trying to achieve? What could go wrong? How does this need to scale?&lt;/p&gt;
&lt;p&gt;This is actually liberating, if you think about it. We can offload the tedious parts—writing boilerplate, implementing standard patterns, translating specifications into code—and focus on the interesting parts: understanding user needs, designing robust systems, making architectural tradeoffs.&lt;/p&gt;
&lt;p&gt;But only if we actually &lt;em&gt;do&lt;/em&gt; the engineering work. If we just prompt “build me X” without providing context, constraints, and architectural guidance, we get that beautiful skyscraper that nobody can live in.&lt;/p&gt;
&lt;h2 id=&quot;how-to-actually-work-with-ai-coding-tools&quot;&gt;How to Actually Work with AI Coding Tools&lt;/h2&gt;
&lt;p&gt;Here’s what I’ve learned about working effectively with LLMs:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Bad prompt:&lt;/strong&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;“Build me a payment system for my SaaS app”&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;Good prompt:&lt;/strong&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;“Build me a payment system with these requirements:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Support credit-based pricing (users buy credits, features consume credits)&lt;/li&gt;
&lt;li&gt;Must handle concurrent credit deductions safely (two API calls shouldn’t both succeed if there’s only enough credits for one)&lt;/li&gt;
&lt;li&gt;Need audit trail for billing disputes&lt;/li&gt;
&lt;li&gt;Should fail gracefully if payment processor is down (degrade to cached credit counts, log for reconciliation)&lt;/li&gt;
&lt;li&gt;Performance requirement: credit check must add &amp;#x3C; 50ms to request latency&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Use PostgreSQL with row-level locking for credit deductions. Implement idempotent credit operations so retries don’t double-charge. Include comprehensive logging for debugging billing issues.”&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The second prompt does the engineering work. It specifies:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The business model (credit-based pricing)&lt;/li&gt;
&lt;li&gt;The critical constraint (safe concurrent access)&lt;/li&gt;
&lt;li&gt;The compliance requirement (audit trail)&lt;/li&gt;
&lt;li&gt;The reliability requirement (graceful degradation)&lt;/li&gt;
&lt;li&gt;The performance requirement (latency budget)&lt;/li&gt;
&lt;li&gt;The technical approach (PostgreSQL with row locks)&lt;/li&gt;
&lt;li&gt;The operational requirement (debuggable)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Now the LLM can write code that actually works in production, because I’ve given it the architectural blueprint.&lt;/p&gt;
&lt;h2 id=&quot;the-uncomfortable-truth&quot;&gt;The Uncomfortable Truth&lt;/h2&gt;
&lt;p&gt;Most of us who’ve been writing code for years have been doing both roles simultaneously for so long that we don’t even notice when we’re switching hats. When we sit down to “code,” we’re actually doing engineering work (thinking about requirements and constraints) &lt;em&gt;and then&lt;/em&gt; programming work (implementing the solution).&lt;/p&gt;
&lt;p&gt;With LLMs, we can’t do that anymore. The programming part gets delegated to the AI. But if we don’t explicitly do the engineering part ourselves—if we just treat the LLM as a magic “build me X” box—we get code that works in the demo but fails in reality.&lt;/p&gt;
&lt;p&gt;And here’s the uncomfortable part: &lt;strong&gt;many people who call themselves engineers are actually just programmers&lt;/strong&gt;. They’re really good at writing code, but they’ve never had to think deeply about system design because they were implementing someone else’s architecture, or working on small enough systems that the complexity didn’t matter.&lt;/p&gt;
&lt;p&gt;With LLMs, this distinction now matters. The code-writing skills that used to be valuable are being commoditized. What’s becoming valuable is the engineering thinking: understanding requirements, designing robust systems, making good architectural tradeoffs, considering failure modes.&lt;/p&gt;
&lt;h2 id=&quot;what-this-means-for-you&quot;&gt;What This Means For You&lt;/h2&gt;
&lt;p&gt;If you’re learning to code right now, don’t just learn syntax and frameworks. Learn to think like an engineer:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;When you build something, ask: “What happens when 1000 people use this simultaneously?”&lt;/li&gt;
&lt;li&gt;Before writing code, ask: “What are all the ways this could fail, and how should it handle each one?”&lt;/li&gt;
&lt;li&gt;Study real-world system design, not just toy examples&lt;/li&gt;
&lt;li&gt;Learn to write specifications that would let someone else implement your vision correctly&lt;/li&gt;
&lt;li&gt;Understand the &lt;em&gt;why&lt;/em&gt; behind patterns, not just the &lt;em&gt;how&lt;/em&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If you’re already working as a developer, pay attention to which role you’re actually performing. Are you mostly implementing features based on specs? You’re in the programmer role, and LLMs are coming for that job. Start developing engineering skills: system design, requirements analysis, architectural thinking.&lt;/p&gt;
&lt;p&gt;If you’re hiring, stop asking “can you code fizzbuzz?” and start asking “design a URL shortener that handles 1000 requests/second and explain your failure modes.” The code-writing part is increasingly automated. The thinking part isn’t.&lt;/p&gt;
&lt;h2 id=&quot;the-punchline&quot;&gt;The Punchline&lt;/h2&gt;
&lt;p&gt;Here’s the irony: I wrote this post with Claude’s help. Not because I couldn’t write it myself, but because Claude is a better &lt;em&gt;programmer&lt;/em&gt; than I am. I gave it my core idea, my architectural guidelines (the blog writing guide), my requirements (make it engaging and human), and my constraints (use the construction analogy, include real examples).&lt;/p&gt;
&lt;p&gt;Claude wrote beautiful prose. But I did the engineering: I decided what to communicate, how to structure the argument, what examples would land, what the reader needs to understand.&lt;/p&gt;
&lt;p&gt;The future of software development isn’t “no human involved.” It’s humans doing what humans are uniquely good at—understanding messy real-world requirements, making tradeoffs with incomplete information, designing robust systems for complex environments—while AI does what it’s uniquely good at: writing clean, correct code that implements those specifications.&lt;/p&gt;
&lt;p&gt;But only if we remember to do the engineering work. Otherwise, we’re just building skyscrapers without architects.&lt;/p&gt;
&lt;p&gt;And we all know how that story ends.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;em&gt;What’s your experience with AI coding tools? Are you finding yourself doing more engineering thinking, or just prompting and hoping? I’d love to hear your stories—the spectacular failures are often more educational than the successes.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;(Written by Human, improved using AI where applicable.)&lt;/em&gt;&lt;/p&gt;</description><pubDate>Thu, 20 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>