codeintelligently
Back to posts
Codebase Understanding

Architecture Decision Records: Documentation That Gets Read

Vaibhav Verma
7 min read
architecturedocumentationengineering culturecodebase understandingdecision records

Architecture Decision Records: Documentation That Gets Read

I've written wikis that nobody opened. I've created Confluence spaces that became digital graveyards within weeks. I've maintained README files that drifted so far from reality they became actively harmful. Then I discovered Architecture Decision Records, and something changed: engineers actually read them.

ADRs work because they answer the question nobody else bothers to document: why. Not what the code does. Not how to use it. Why it was built this way instead of some other way.

What Is an ADR?

An Architecture Decision Record is a short document that captures a single architectural decision, the context around it, the options considered, and the rationale for the choice made. That's it. One decision, one document, typically one to two pages.

The concept was formalized by Michael Nygard in 2011, but the idea is older than software. Legal professionals write case opinions. Scientists write lab notebooks. Military officers write after-action reports. ADRs are the engineering equivalent.

Why Most Documentation Fails (and ADRs Don't)

Traditional documentation fails for three reasons:

It tries to describe the current state. The moment code changes, the documentation is wrong. Keeping docs synchronized with code is a losing battle. I've never seen a team win it at scale.

It lacks motivation. A wiki page that says "We use PostgreSQL for the primary database" tells you nothing useful. You can see that from the code. What you can't see from the code is why you chose PostgreSQL over DynamoDB, what constraints drove that choice, and under what circumstances you'd reconsider.

Nobody owns it. Documentation written by everyone is maintained by no one.

ADRs sidestep all three problems. They describe decisions, not state. Decisions don't change (they can be superseded, but the original record stays valid as history). They're inherently motivational because the whole point is "why." And they have clear ownership because the author is the person who made the decision.

The ADR Template That Works

I've iterated on this template across three companies. This version hits the sweet spot between thoroughness and brevity.

markdown
# ADR-{number}: {Title}

**Status:** Proposed | Accepted | Deprecated | Superseded by ADR-{number}
**Date:** YYYY-MM-DD
**Author:** Name
**Reviewers:** Name, Name

## Context

What is the situation that requires a decision? What problem are we solving?
What constraints exist? (2-4 paragraphs)

## Decision

What is the decision? State it clearly in one sentence, then elaborate.

## Options Considered

### Option A: {Name}
- Pros: ...
- Cons: ...
- Estimated effort: ...

### Option B: {Name}
- Pros: ...
- Cons: ...
- Estimated effort: ...

### Option C: {Name} (if applicable)
- Pros: ...
- Cons: ...
- Estimated effort: ...

## Rationale

Why did we choose this option? What were the deciding factors?
What tradeoffs are we accepting? (2-3 paragraphs)

## Consequences

What are the positive and negative consequences of this decision?
What will we need to do as a result? What doors does this close?

## References

Links to relevant RFCs, blog posts, docs, or Slack threads.

A Real Example

Here's a slightly anonymized ADR from a real project:

markdown
# ADR-014: Use Event Sourcing for Order Management

**Status:** Accepted
**Date:** 2025-08-15
**Author:** Vaibhav Verma
**Reviewers:** Sarah Chen, Marcus Johnson

## Context

Our order management system currently uses a CRUD model with a
single `orders` table. We've been asked to implement full order
history, audit logging, and the ability to replay past states for
dispute resolution. Our current approach of adding columns like
`previous_status` and `status_changed_at` is becoming unwieldy.
We have 47 columns on the orders table, 12 of which are audit-related.

We process approximately 15,000 orders per day. Order state
transitions average 4.2 per order. Our SLA requires 99.9% uptime
for order operations.

## Decision

We will implement event sourcing for the order aggregate.
Order state will be derived from an append-only event log rather
than stored as mutable rows.

## Options Considered

### Option A: Expand the CRUD Model
- Pros: No architectural change, team already knows the pattern
- Cons: Table continues to grow, audit queries become slower,
  no true state replay capability
- Estimated effort: 2 weeks

### Option B: Event Sourcing with Custom Implementation
- Pros: Full control, tailored to our needs
- Cons: Significant development effort, event store reliability
  is our responsibility
- Estimated effort: 6-8 weeks

### Option C: Event Sourcing with EventStoreDB
- Pros: Purpose-built event store, built-in projections,
  proven at scale
- Cons: New infrastructure dependency, team learning curve,
  operational overhead
- Estimated effort: 4-5 weeks

## Rationale

We chose Option C. The deciding factors were: (1) our team has no
event sourcing experience, so a purpose-built store reduces risk,
(2) EventStoreDB's projection system handles our read-model
requirements without custom code, (3) the 15,000 orders/day
volume is well within EventStoreDB's documented capabilities.

We explicitly chose not to build our own event store (Option B)
because we've been burned before by building infrastructure
in-house when proven solutions exist.

## Consequences

Positive: Full audit trail, state replay, clean separation of
write and read models.

Negative: Team needs EventStoreDB training (estimated 1 week).
Deployment complexity increases (new database to manage).
Local development setup needs updating.

We accept that eventual consistency between the event store and
read models means order list views may lag by up to 500ms.

This ADR is worth more than a 50-page architecture document. Six months later, when a new developer asks "why are we using EventStoreDB?", the answer is right here: because of specific constraints, after considering specific alternatives, with explicit tradeoffs acknowledged.

The Process: When and How to Write ADRs

When to Write an ADR

Not every decision needs an ADR. I use this decision tree:

  1. Is this decision easily reversible? If yes, don't write an ADR. Just make the decision.
  2. Does this decision affect more than one team? If yes, write an ADR.
  3. Will someone ask "why did we do it this way?" in 6 months? If yes, write an ADR.
  4. Does this decision involve a significant tradeoff? If yes, write an ADR.

In practice, this means 2-5 ADRs per quarter for a team of 6-8 engineers. If you're writing more than that, you're being too granular. If you're writing fewer, you're missing important decisions.

Where to Store ADRs

In the codebase. Not in Confluence. Not in Notion. Not in Google Docs. In a docs/adr/ directory at the root of the repository. ADRs stored outside the codebase become invisible. ADRs in the repo show up in code search, appear in PRs when updated, and travel with the code when it's forked or migrated.

docs/
  adr/
    001-use-nextjs-app-router.md
    002-postgresql-over-dynamodb.md
    003-monorepo-with-turborepo.md
    ...
    014-event-sourcing-for-orders.md

The Review Process

ADRs should go through PR review, just like code. The author writes a draft, tags 2-3 reviewers, and the team discusses in PR comments. This is intentional. The PR discussion becomes part of the historical record. When someone reads the ADR later, they can click through to the PR and see the debate.

My Contrarian Take: ADRs Should Document Rejected Options in Detail

Most ADR templates give a brief nod to alternatives. I think the alternatives section should be the longest part of the document.

Here's why: the decision itself is usually obvious in hindsight. "Of course we chose PostgreSQL." But why didn't we choose DynamoDB? What would have made DynamoDB the right choice? Under what conditions should we reconsider?

Documenting rejected options does two things. First, it prevents re-litigation. When a new team member says "we should switch to DynamoDB," you don't have to rehash the entire discussion. Point them to the ADR. If the constraints have changed, great, write a new ADR. If they haven't, move on.

Second, it captures institutional knowledge about the problem space. The ADR becomes a mini research document about the decision domain. A well-written alternatives section teaches people about tradeoffs they might not have considered.

The ADR Maturity Checklist

Use this to evaluate your ADR practice:

  • ADRs are stored in the repository, not an external tool
  • ADRs are numbered sequentially (ADR-001, ADR-002, etc.)
  • Each ADR has a clear status (Proposed, Accepted, Deprecated, Superseded)
  • ADRs go through PR review before acceptance
  • The team writes 2-5 ADRs per quarter
  • New team members are pointed to ADRs during onboarding
  • Superseded ADRs link to their replacement
  • ADRs document at least 2 rejected alternatives in detail
  • ADRs include explicit tradeoffs and consequences
  • ADRs are referenced in code comments where the decision manifests

Getting Started

If your team has zero ADRs today, here's the play: pick the last architectural decision that caused debate. Write the ADR retroactively. Put it in a PR. Let the team see the format. Then commit to writing one ADR for each significant decision going forward.

Don't try to document every past decision. That's a documentation project, and documentation projects die. Just start capturing decisions from today. In six months you'll have 15-20 ADRs, and that collection will be the most useful documentation your team has ever produced.

The bar isn't perfection. It's existence. A rough ADR that captures the "why" is infinitely more valuable than no ADR at all.

$ ls ./related

Explore by topic