Skip to content

08 · Architecture Decision Records (ADRs) & Evolution

Last chapter you learned to design a system from 0. But finishing the design isn't the end — the system will grow with the business, be handed to others, and keep running long after you've forgotten "why we did it this way." This chapter is about making architecture live long and grow well.


Opening: the first thing lost is always the "why"

Picture this. You inherit a system someone designed three years ago. You open the code and find a strange design: order data is written into two different stores at once, with reconciliation logic in between. Your first reaction: "Isn't this redundant? Just write to one!" So you wield your pen, delete one store, merge the logic —

Three weeks later, production blows up. Turns out the original design existed because a regulation required order records to land in an immutable store for audit, while the primary database had to support high-frequency order edits — two demands that couldn't be met by one store. That reason was written nowhere in the code, and the person who made the call left long ago.

This is architecture's most fatal information loss: code and diagrams tell you what the system is, but almost never explain why it was chosen this way, and what was given up. And it's the latter that whoever inherits it needs most — and loses most easily.

The "we decided this because…" said out loud evaporates completely after three meetings, two resignations, and a year. When you finally need it, all that's left is a baffling-looking design and a group of people afraid to touch it.

This chapter's cure is simple — so simple that many disdain it: record the important architecture decisions, one page each. That page is called an ADR.


1. ADR: keeping a ledger of "why" for your architecture

ADR = Architecture Decision Record. It's a lightweight doc; every time you make an important architecture decision, you write one, capturing the decision's full story.

Its essence is in "lightweight":

  • One decision per record, usually one page, readable in minutes.
  • Append, don't modify: when a decision changes, write a new one and mark the old as "superseded" — preserve history, don't erase it. Because "we once thought this, then changed it for that reason" is itself valuable.
  • Lives with the code (e.g. a docs/adr/ directory in the repo), so it's version-controlled, searchable, and reviewed alongside the code.

An ADR template you can copy outright

┌──────────────────────────────────────────────────────────┐
│  ADR-007: Order records use "primary DB + immutable log"  │
│           dual-write                                       │
├──────────────────────────────────────────────────────────┤
│                                                            │
│  Status: Accepted                                          │
│       (Draft / Accepted / Deprecated / Superseded by 015)  │
│                                                            │
│  Date: 2026-05-23   Deciders: Architecture + Compliance    │
│                                                            │
│  ── Context ─────────────────────────────────────────     │
│  Regulation requires order records to land in a            │
│  "write-once, immutable" store for audit; but the          │
│  business needs orders to be edited frequently (address,   │
│  quantity). A single store can't be both editable and      │
│  immutable.                                                │
│                                                            │
│  ── Decision ───────────────────────────────────────      │
│  Primary DB holds mutable order state for high-frequency   │
│  read/write; every change is also appended to an immutable │
│  log as the audit basis; reconcile periodically against    │
│  the log.                                                  │
│                                                            │
│  ── Options considered ──────────────────────────────      │
│  A. Primary DB + soft-delete flag → rejected: soft-deletes │
│     are still mutable, fails audit                         │
│  B. Immutable store only → rejected: can't support edits   │
│  C. Dual-write (this) → accepted                           │
│                                                            │
│  ── Trade-offs & consequences ──────────────────────       │
│  + Satisfies both compliance and business hard constraints │
│  − Introduces dual-write; needs reconciliation to backstop │
│    inconsistency (known complexity)                        │
│  − Successors will think it "redundant" — this ADR is      │
│    written for you: don't delete it!                       │
│                                                            │
└──────────────────────────────────────────────────────────┘

The template is just six blocks; memorize these six headings and you're set:

FieldWhat to writeWhy it can't be skipped
TitleOne sentence stating the decisionEasy to find later
StatusDraft / Accepted / Deprecated / Superseded by XTells readers whether it still holds
ContextWhat problem and constraints you facedWhat successors lack most is "the situation back then"
DecisionWhat you finally decidedThe "what" — also visible in code
Options consideredWhat else you weighed, and why not"What was given up" is the ADR's unique value
Trade-offs & consequencesThe upside, the cost, the known debtLets successors inherit "this was a deliberate trade-off"

Note the callback to last chapter: in Step ⑧ of Chapter 07, you were repeatedly pushed to say "I chose A, gave up B, because…" and to list "open questions." Those words, verbatim, are an ADR's Decision + Options + Trade-offs. ADR isn't extra burden — it just puts on paper what you should have thought through anyway.


2. Strong opinion: recording "why" matters 100× more than "what"

This is the one principle this chapter — and arguably the whole repo — most wants you to remember:

Code, diagrams, API docs all record "what" — what the system looks like now, what modules it has, how they call each other. These you can "read" from the system itself anytime.

But "why it's this way, what was given up, what's being traded for what" — that can't be read out. It lives only in the head of whoever decided, and vanishes if unwritten.

Why is "why" so precious? Three reasons:

  1. "What" is self-evident; "why" isn't. Reading code tells you what the system is; but code never tells you "we wanted the simpler option B, and only took the complex C because of some constraint."
  2. Without "why," successors are stuck between two errors: either afraid to touch ("this looks weird, but who knows what breaks if I change it" → the system ossifies), or recklessly changing (like the dual-write deletion above, stepping on a landmine). With "why," successors can safely judge "is this constraint still here, does this decision still hold."
  3. "Why" is transferable wisdom; "what" is a one-off result. A good ADR's reasoning of "we sacrificed B for A" can be reused on another project — exactly the belief this repo holds throughout.

💡 In one line: good docs don't explain "what the code does" (the code says that itself); good docs explain "why the code does it this way" (the code never says that). Spend your recording effort on what's gone forever once lost.

This extends the first of the root README's three reading principles: "Ask 'why' before 'how'. If you can't see the trade-off, you haven't understood it." — ADR turns that principle from "reading" into "writing."


3. Evolutionary architecture: don't aim to get it right once — leave good "seams"

Chapter 07 kept saying "architecture is iterated." But "being iterable" itself must be designed — a bad architecture, where changing one thing shakes everything, simply can't be iterated.

The core idea of Evolutionary Architecture:

Since you're doomed to not get it right in one shot, design the architecture to be "easy to get right later." The point isn't "perfect now," but "able to cheaply replace a piece in the future."

How to achieve "easy to change right"? The answer is leaving good seams — clear module boundaries and interfaces. A system with good seams looks like:

   No seams (a mud ball):              Good seams (modular):

   ┌─────────────────────┐           ┌────────┐  iface  ┌────────┐
   │  all logic in one    │           │ Mod A  │◀──────▶│ Mod B  │
   │  blob: payment,      │           └────────┘        └───┬────┘
   │  inventory, notify   │                                 │ iface
   │  all calling each     │           ┌────────┐  iface ┌──▼─────┐
   │  other directly       │           │ Mod D  │◀──────▶│ Mod C  │
   └─────────────────────┘           └────────┘        └────────┘
   Want to swap "notify"?             Want to swap module C?
   → impossible, tangled with all     → unplug it, swap one in; as long
                                         as the interface holds, others
                                         don't notice

The value of a seam is isolating "future uncertainty." A few examples from last chapter's URL shortener:

  • You make "short-code generation" an independent module with a clear interface. v1 uses "pre-allocated ID ranges"; if you later want a different strategy, only this block changes — the redirect service and storage know nothing.
  • You hide "caching" behind the redirect service's interface. v1 uses in-process cache; at scale you swap to CDN — transparent to callers.

This is exactly the wisdom each template's "Section 12 · Evolution Path" conveys: in the AI Chat Product evolution path, the MVP calls an external model API directly, and only in the growth stage swaps to self-hosted inference + continuous batching — it's swappable precisely because "calling the model" was sealed behind a clean boundary from the start. Good seams turn evolution from "rewrite" into "replace."

💡 When designing, ask one more question: "which block is most likely to be replaced later?" Wrap the most-likely-to-change part in the cleanest interface. This trades "a little more thought now" for "a lot less pain later."


4. Technical debt: not all bad — the key is "keeping a ledger"

"Technical debt" is often treated as purely negative — as if owing debt means you did something wrong. That's a misconception.

The true meaning of tech debt, borrowed from finance: deliberately choosing an expedient solution that will need repaying later, in order to "get value faster now." Like borrowing money — borrowing isn't wrong; the key is whether you know you borrowed, and whether you intend to repay.

Two kinds of tech debt, to be distinguished:

   ┌───────────────────────────┬───────────────────────────┐
   │   Conscious debt (healthy)  │  Unconscious debt (danger) │
   ├───────────────────────────┼───────────────────────────┤
   │ "To hit the release, use   │ "I don't know why it's     │
   │  the simplest approach,     │  written this way, but it  │
   │  log a TODO, refactor next  │  runs"                     │
   │  quarter"                   │                            │
   │                            │                            │
   │ → a rational business call  │ → out of control; you don't│
   │ → in ADR/backlog, scheduled │   even know you owe        │
   │   for repayment             │ → blows up one day,        │
   │                            │   completely unprepared    │
   └───────────────────────────┴───────────────────────────┘

For an MVP, deliberately taking on tech debt is often correct — the URL shortener's v1 "single store + single-node ID generation" in Chapter 07 is a debt, but it bought "fast launch, validate the need," entirely worth it. The error isn't owing debt, but:

  • not knowing you owe debt (unconscious debt, most dangerous);
  • not recording the debt (you still forget "why," back to the chapter's opening pit);
  • only borrowing, never repaying (debt snowballs until the system can't be changed).

The discipline for tech debt is just three rules: ① borrow consciously (be clear it's expedient); ② write it down (a piece of tech debt is an ADR to write — context being "why we're cutting this corner for now"); ③ schedule repayment (put it in the backlog with a "repay when X happens" trigger, not "someday").

Manage tech debt as "a liability on the books, repaid on schedule," not "dirt swept under the rug, pretended away" — this is the watershed between a mature team and an amateur outfit.


5. Conway's Law: your architecture will look like your organization

You can't discuss architecture evolution without one "law." It's not a technical rule but a sociological observation, yet it profoundly shapes every system's final form:

Conway's Law: a system's architecture inevitably comes to resemble the communication structure of the organization that designed it.

In plain words: the interface between two modules looks like the way the two groups of people behind those modules communicate. If two teams meet daily and sit together, their two modules will likely be tightly coupled; if two teams are in different departments communicating only by email, their modules will naturally form clear (even rigid) boundaries.

   Org looks like this:                  System grows like this:

   ┌─────────┐   ┌─────────┐            ┌─────────┐   ┌─────────┐
   │ Payment  │   │ Logistics│   ───▶    │ Payment  │   │ Logistics│
   │ (separate)│   │ (separate)│           │ (separate │   │ (separate│
   └─────────┘   └─────────┘            │  deploy)  │   │  deploy) │
        barely talk, each own theirs     └────┬────┘   └────┬────┘
                                              └─ clean, loose ─┘

   ┌───────────────────────┐            ┌───────────────────────┐
   │ one big team does all  │   ───▶     │ one big monolith,      │
   └───────────────────────┘            │ everything tangled     │
                                        └───────────────────────┘

Conway's Law gives two crucial lessons for architecture evolution:

  1. Before splitting services, look at team structure. You want to split a monolith into microservices (Chapter 04), but if the team is still one big mush where everyone does everything, the "microservices" you carve out will still be tightly coupled — if architecture boundaries don't align with team boundaries, splitting is futile. Want loosely coupled services? First have clearly bounded teams that can own them independently.
  2. Use it in reverse (Inverse Conway Maneuver): to get the architecture you want, first organize teams that shape. Want the system to be a few independently evolving services? Then first split people into small squads that can decide independently, and the architecture will naturally grow that way. Organizational design is itself a form of architecture design.

💡 Conway's Law's essential reminder: architecture problems are often organizational problems in disguise. When two modules just won't decouple cleanly, don't rush to change code — check whether the two groups behind them are inherently inseparable. Many "architecture problems" have their real solution in org structure, not in tech.


6. When should you refactor / upgrade the architecture? (Use judgment, not feelings)

As the system grows, you'll eventually face: should this architecture change now? Beginners often decide on two bad bases:

  • By feeling: "this code looks ugly, let's refactor" — ugly is not a reason to upgrade architecture; ugliness that affects no quality attribute can wait.
  • By trend: "big companies all went microservices/platform/some-new-architecture, so should we" — their bottleneck isn't yours; copying a big company's mature-stage architecture onto your growth stage is the #1 over-engineering.

So what to judge by? The two rulers from the first six chapters: bottlenecks and quality attributes.

   Should you upgrade? — measure with these two rulers, not guesswork:

   ┌────────────────────────────────────────────────────────┐
   │  Ruler 1: bottleneck (from 06 + Step ⑦ of 07)           │
   │   Is some quality attribute already capped by the arch? │
   │   • DB reads maxed, no amount of cache helps anymore?   │
   │   • One module's change always drags others; releases   │
   │     getting slower?                                     │
   │   → A real, measured bottleneck is the upgrade signal   │
   ├────────────────────────────────────────────────────────┤
   │  Ruler 2: a shift in quality attributes (from 06)       │
   │   Has the business's "how good" bar gone up a level?    │
   │   • Availability from 99% to 99.99%?                    │
   │   • User/data volume into the next order of magnitude   │
   │     (the "100×" from 07's estimation)?                  │
   │   → Goals changed, old arch can't hold → upgrade        │
   └────────────────────────────────────────────────────────┘

Crystallize the judgment into one sentence:

Don't upgrade because "the architecture is old/ugly," but because "a quality attribute I genuinely care about is being capped by the current architecture, and that cap is a real, measured bottleneck." Upgrading architecture is a costly, risky move; it must be in exchange for a specific quality attribute you need right now — exactly the way Chapter 06 · Quality Attributes & Trade-offs teaches you to judge.

And every "decision to upgrade the architecture" is itself a major decision worth an ADR: context (what bottleneck forces us), decision (upgrade to what), options (how else to cope), trade-offs (what we pay for this upgrade). — See, all this chapter's tools loop back to ADR.


Chapter summary

  • The first thing lost is always "why": code and diagrams say what the system is, but not "why it was chosen, what was given up" — and the latter is what successors need most and lose most easily.
  • ADR (Architecture Decision Record) is the cure: a lightweight, append-don't-modify doc kept with the code, recording each important decision. Six blocks: Title, Status, Context, Decision, Options considered, Trade-offs & consequences.
  • Strong opinion: recording "why" matters 100× more than "what." Spend your recording effort on what's gone forever once lost.
  • Evolutionary architecture: don't aim to get it right once; design the system to be "easy to change right" — leave clean seams (module boundaries/interfaces) where change is most likely, turning evolution from "rewrite" into "replace."
  • Tech debt isn't all bad: it's a conscious expedient. The discipline is "borrow consciously, write it down, schedule repayment"; the danger is unconscious, unrecorded, never-repaid debt.
  • Conway's Law: architecture comes to resemble the org's communication structure. Look at team boundaries before splitting services; or use it in reverse — to get an architecture, first form the teams. Architecture problems are often organizational problems in disguise.
  • When to upgrade: judge, don't feel: not "it's old/ugly/others did it," but "a quality attribute I truly care about is capped by a real bottleneck" — measure with 06's rulers, don't wing it.

📌 Real case: the origin of ADRs

The ADR in this chapter comes from Michael Nygard's 2011 post Documenting Architecture Decisions — he proposed recording each decision's Context / Decision / Status / Consequences in small lightweight files. The format later became an industry standard.

  • 📎 Community resources, templates & tools: adr.github.io
  • Each template's "Key Decisions & Trade-offs" section in this repo is essentially a set of ready-made ADRs.

Conclusion: become someone who "keeps making good judgments, and keeps recording them"

You've reached the end of the tutorial. Let's return to the very beginning, to why this repo exists (root README):

Writing code is disappearing, while architectural judgment is becoming scarcer and more valuable than ever.

Across these chapters, what you learned was never a language or a framework — those expire; this year's trend changes next year. You learned something that doesn't depreciate:

   Depreciating (AI is making cheap):   Not depreciating (ever scarcer):

   • how to write some syntax            • take a vague need, ask the right
   • some framework's API                  questions
   • some boilerplate                    • make grounded choices amid trade-offs
   • a one-off implementation            • see where the system dies, what to swap
   ─────────────────────────            • distill "why" into reusable wisdom
   → hand it to AI                       ──────────────────────────────────
                                         → THIS is the core of the future dev

Having finished these last two chapters, you should have a fuller picture of "the great architect" than in Chapter 01. They're not just "someone who designs good systems" (that's 07), but someone who keeps making good architectural judgments, and distills every judgment and its reasoning (that's 08).

Because a single good design rusts over time, but the loop of "keep judging + keep recording" makes you and your system stronger together:

        ┌─────────────────────────────────────────┐
        │                                         │
        ▼                                         │
   make an architecture judgment ──▶ record "why" as an ADR
   (using methods from 01-07)         │            │
                                      ▼            │
          business grows / new bottleneck ◀────────┘
          (re-judge with 06's rulers, back to top)

          judgment compounds with every loop

Code changes, frameworks change, even the trendy word for "architecture" will turn over generation after generation. But thinking through what the system should look like before you act, and being able to explain why each choice was made — that judgment keeps appreciating across your entire career. That is what this repo wants to give you.


One last exercise, then you're on your own

Read templates as ADRs. Go back to templates/, pick any system, and head straight to its Section 8, "Key Decisions & Trade-offs."

You'll find each entry there is a compressed ADR — it has context (the choice faced), a decision (the leaning), options given up, and trade-offs (the cost). Try taking the AI Chat Product's "Decision 1: streaming vs one-shot return" and hand-expanding it into a full ADR using this chapter's six-block template.

Do that, and you'll hold both halves of this tutorial at once: judging like an architect (07), and preserving judgments like an architect (08). The rest is to go practice, one real system at a time.