paulserban.eu

Writing Edition

Paul Serban

Writing, snippets & book notes

Architecture Decision Records: The Engineering Practice Your Team Is Probably Skipping

Documenting Design Choices That Actually Matter

Introduction

Every software system carries two kinds of debt: the kind you can see in the code, and the kind buried in the minds of the people who built it. The second kind is far more dangerous. When the senior engineer who chose PostgreSQL over MongoDB leaves the company, or when a new team member refactors a "weird" caching layer that was actually doing something critical - the cost of undocumented decisions becomes painfully apparent.

Architecture Decision Records, commonly referred to as ADRs, are a lightweight practice designed to make that hidden knowledge explicit. First formalized by Michael Nygard in 2011 in his Cognitect blog post Documenting Architecture Decisions, the ADR is a short plaintext document that captures a single significant architectural decision, the context in which it was made, the options that were considered, and the rationale behind the final choice. Nothing more, nothing less.

ADRs are not architecture diagrams. They are not design documents or RFCs. They are decisions - the atoms of architectural thought - recorded so that future engineers can understand not just what a system does, but why it does it that way. This distinction matters more than it might initially appear.

The Problem ADRs Solve: Architectural Amnesia

Software systems evolve continuously, but institutional knowledge does not accumulate at the same pace. Teams grow, shrink, and turn over. Codebase understanding fragments across individuals. What begins as a well-reasoned design choice gradually becomes an obstacle that later engineers work around, replace, or inadvertently break - because no one left a note.

This phenomenon, sometimes called "architectural amnesia," is not a failure of intelligence or diligence. It is a structural problem. Without an explicit mechanism for recording decisions, knowledge leaks. Junior engineers inherit constraints without explanations. Senior engineers spend cycles in meetings re-litigating decisions that were settled years ago. Code reviews devolve into debates over preferences when the real question - "why was it built this way originally?" - goes unanswered.

The consequences are measurable. Teams revisit the same architectural debates repeatedly, draining velocity. Systems accumulate workarounds for "legacy" constraints that would have been fine if only someone had explained their purpose. Onboarding takes longer than necessary. Incident post-mortems surface the same root cause: someone changed something that seemed arbitrary but wasn't.

ADRs interrupt this cycle at the source. By making decision rationale a first-class artifact of the development process, they transform ephemeral knowledge into durable institutional memory. The record does not replace conversation - it captures the outcome and context of the conversation so that the next conversation can start from a more informed position.

What an ADR Actually Contains

The canonical ADR format, as popularized by Nygard and refined by subsequent practitioners, is intentionally minimal. A well-formed ADR has five sections: title, status, context, decision, and consequences. Some teams add a sixth section for alternatives considered, which is often the most valuable part.

Title is a short, imperative phrase describing the decision. "Use PostgreSQL for transactional data" is good. "Database decision" is not. The title should be specific enough that someone skimming a list of ADRs can identify the relevant record quickly.

Status tracks the lifecycle of the decision: Proposed, Accepted, Deprecated, or Superseded. The Superseded status is particularly important - when a decision is replaced by a newer one, the old ADR should link to the new one rather than being deleted. This preserves the historical chain and explains why something changed.

Context describes the situation, constraints, and forces that made this decision necessary. A good context section is honest about trade-offs, constraints (including organizational and timeline constraints), and the specific problem being solved. This is not a place for advocacy - it is a neutral description of the landscape.

Decision is a single, unambiguous statement of what was chosen. It should be active and declarative: "We will use Redis for session storage" rather than "Redis was considered and found to be suitable."

Consequences lists both the positive and negative outcomes of the decision. The honest acknowledgment of trade-offs is what distinguishes a useful ADR from a decision log that only records successful choices. Future engineers need to know what the decision gave up, not just what it gained.

Here is a concrete example of a minimal but complete ADR:

# ADR-007: Use CQRS Pattern for Order Processing Domain

## Status

Accepted

## Context

The order processing domain has divergent read and write workloads. Write operations
require strict transactional consistency and complex validation logic. Read operations
need to serve denormalized views to multiple consumers (customer-facing API, reporting
dashboard, fulfillment service) at low latency.

Using a single data model for both reads and writes has led to increasingly complex
queries, over-fetching, and coupling between the order aggregation logic and the
read-side data requirements. The application currently processes approximately 3,000
order events per hour with spikes to 15,000 during peak periods.

## Decision

We will implement the Command Query Responsibility Segregation (CQRS) pattern for
the order processing bounded context. Command handlers will write to a normalized
PostgreSQL schema. Read models will be maintained as denormalized projections in
separate tables, updated via domain event handlers. The write model will remain
the source of truth; read models are derived and rebuildable.

## Consequences

**Positive:**

- Read queries are decoupled from write model complexity
- Each read model can be optimized for its specific consumer
- Read models can be rebuilt from event history if shape changes

**Negative:**

- Introduces eventual consistency between write and read sides
- Increases operational complexity: projection handlers must be monitored
- Higher cognitive overhead for engineers unfamiliar with CQRS

## Alternatives Considered

- **Single shared model**: Simpler, but increasingly unworkable at current complexity
- **GraphQL with DataLoader**: Addresses over-fetching but not write complexity
- **Separate read replicas**: Reduces read load but doesn't address model mismatch

This record took perhaps thirty minutes to write. It will save hours of confusion for every engineer who works on this system in the next five years.

Where ADRs Live: Tooling and Workflow Integration

An ADR that lives in a shared Google Doc and is never linked from the codebase is, in practical terms, already lost. The placement of ADRs matters almost as much as their content, because discoverability determines whether they actually get used.

The dominant convention is to store ADRs alongside the codebase in version control, typically in a docs/adr/ or docs/decisions/ directory. This approach has significant advantages: ADRs are versioned with the code they describe, they appear in pull request diffs, they are searchable with standard tooling, and they are present when engineers clone the repository. A new team member who opens the project directory and sees docs/adr/ containing fifty numbered records gets an immediate signal that this team takes decision documentation seriously.

For teams that prefer a more structured workflow, tools like adr-tools (a command-line utility by Nat Pryce) automate ADR creation, numbering, and status management. It can be installed via standard package managers and integrates cleanly with git workflows:

# Install adr-tools (macOS)
brew install adr-tools

# Initialize the ADR directory in a project
adr init docs/adr

# Create a new ADR
adr new "Use Redis for distributed session storage"
# Creates: docs/adr/0042-use-redis-for-distributed-session-storage.md

# Supersede an existing ADR
adr new -s 42 "Use JWT for stateless authentication instead of session storage"
# Creates a new ADR and marks ADR-0042 as superseded

Teams working primarily in documentation platforms like Confluence or Notion can maintain ADRs there, but should link from the codebase's README.md to the decision log. The critical requirement is that ADRs are reachable from the place where engineers spend most of their time - the code.

For organizations running multiple services or repositories, a centralized Architecture Decision Log (ADL) maintained in a dedicated repository or documentation site - generated from ADR files across repositories using tooling like log4brains - provides an aggregated view of cross-cutting architectural choices without disrupting per-service ADR ownership.

The ADR Lifecycle: From Proposal to Historical Record

ADRs are not filed and forgotten. They pass through a lifecycle that mirrors the status of the decision they describe, and managing that lifecycle is a significant part of the practice's value.

A decision begins as Proposed, typically in the form of a pull request against the repository. This is the moment for review, discussion, and refinement. Reviewers can comment on context that was missed, alternatives that were not considered, or consequences that were underestimated. The pull request diff becomes a natural forum for asynchronous architectural discourse, with the record itself as the artifact under review. This is substantially more tractable than trying to reconstruct the reasoning for a decision that was made verbally in a meeting two years ago.

Once consensus is reached, the ADR moves to Accepted. This is its default stable state, and it should remain there as long as the decision holds. The only other transitions are to Deprecated (the decision no longer applies, perhaps because the technology was removed) or Superseded (replaced by a newer decision, with a forward link to the replacement ADR).

Critically: old ADRs should never be deleted or retroactively edited to reflect hindsight. The historical record of what was decided, and why, is exactly the artifact future engineers need. An ADR that says "we chose MySQL because at the time we had no in-house expertise with PostgreSQL" is more honest and more useful than one that was quietly updated after the team switched databases.

The revision history in git preserves the context of when a decision was made, which is often as important as why. A decision that looks puzzling in 2025 may make complete sense once you see it was made in 2019, under a different scale, different team composition, or before a particular cloud service became widely available.

Implementation in Practice: Getting Your Team to Actually Write ADRs

The most common failure mode for ADR adoption is not technical - it is cultural. Teams agree that ADRs are a good idea, create a template, write two or three records during the initial enthusiasm, and then quietly abandon the practice as delivery pressure accumulates. Months later, the docs/adr/ directory contains exactly three ADRs from the kick-off week, and the practice is effectively dead.

Sustained adoption requires making ADR creation a natural part of the existing workflow rather than an extra step. The most effective mechanism is the pull request template. Adding an ADR checklist item to the PR template for architecture-affecting changes creates a low-friction nudge without requiring a separate process:

## Architecture Impact

- [ ] This change introduces or modifies a significant architectural decision
  - If checked, link the relevant ADR: `docs/adr/XXXX-decision-title.md`

This approach works because it intercepts the decision at the moment it is being made, when the context is freshest and the cost of documentation is lowest. Writing an ADR three months after a decision is substantially harder and less accurate than writing it during the pull request that implements the decision.

A second enabler is establishing a clear threshold for what constitutes an "architecture decision" worth documenting. Not every implementation choice needs an ADR. A useful heuristic: if the decision would take more than five minutes to explain to a new team member, or if reversing it would require coordinated effort across multiple components, it warrants a record. Decisions about frameworks, data storage engines, authentication strategies, communication patterns (sync vs. async), and external service dependencies almost always qualify. Decisions about variable naming or file organization rarely do.

Teams should also resist the temptation to make ADRs comprehensive design documents. The discipline is in the constraint. An ADR that takes three hours to write will not become a habit. An ADR that takes twenty to thirty minutes - because the team has internalized the format and knows what belongs in each section - will.

Trade-offs and Common Pitfalls

Like any engineering practice, ADRs have failure modes that are worth anticipating rather than discovering the hard way.

The retrospective trap. Writing ADRs retroactively for decisions already made is better than nothing, but substantially worse than writing them prospectively. Retrospective ADRs tend to rationalize the outcome rather than honestly document the alternatives and trade-offs that existed at decision time. If you are bootstrapping an ADR practice on an existing codebase, focus on documenting decisions being made now, and treat retrospective documentation as a lower-priority effort.

Format overengineering. The desire to be thorough can produce ADR templates with fifteen sections, mandatory stakeholder sign-off fields, and integration with ticketing systems. This overhead kills the practice. Start with the five canonical sections. Add fields only when a concrete need emerges, not speculatively.

Treating ADRs as policy, not history. An ADR records what was decided, not what must be done forever. Teams sometimes resist updating the status of superseded ADRs because it feels like admitting a mistake. The opposite is true: an ADR that says "Superseded by ADR-0071" is evidence of a team that thinks carefully about its architecture and updates its record accordingly. ADRs should track the evolution of thinking, not freeze it.

No linkage from code. An ADR file that is never linked from the code it describes is hard to discover. Where a decision directly affects a specific module or service, a comment linking to the ADR adds significant value:

// Session management uses Redis with a short TTL (15 minutes).
// The decision to use Redis over a DB-backed session store is documented in:
// docs/adr/0042-use-redis-for-distributed-session-storage.md
export class SessionService {
  private readonly TTL_SECONDS = 900;
  // ...
}

This creates a bidirectional trail: the ADR explains the code, and the code references the ADR.

Siloed authorship. ADRs written unilaterally by a single architect and announced to the team are less useful than ADRs drafted collaboratively. The process of writing the context section - particularly the alternatives considered - often surfaces disagreement, missed constraints, and better solutions. The discussion is part of the value; the record is the artifact that preserves it.

Best Practices Summary

The following practices, drawn from teams that have successfully sustained ADR adoption over multiple years, are the highest-leverage behaviors:

Store ADRs in version control alongside the codebase, numbered sequentially, in a dedicated directory. Use a consistent naming convention - NNNN-short-decision-title.md - that sorts chronologically and is human-readable. Never delete or retroactively alter a record; use status transitions and forward links instead.

Write ADRs at decision time, not after. The context section degrades rapidly in accuracy as the team moves on to other problems. The pull request that implements the decision is the natural trigger.

Keep the format lightweight. The five canonical sections (title, status, context, decision, consequences) cover the vast majority of what future engineers need. Extend the template only when consistent patterns of missing information emerge.

Include the alternatives considered, even briefly. This is the section that most effectively communicates the engineering thinking behind a decision and prevents "why didn't you just use X?" questions from new team members.

Review ADRs as part of architectural reviews and post-mortems. When a system behaves unexpectedly or a decision turns out to have been wrong, revisiting the relevant ADR - and creating a new one to document the updated understanding - closes the feedback loop that makes the practice self-improving.

Key Takeaways: Five Steps to Start Today

1. Create the directory and template now. Add docs/adr/0001-use-adrs-to-record-architectural-decisions.md to your repository - a meta-ADR documenting the decision to use ADRs. This establishes the convention and seeds the directory.

2. Add an ADR checkpoint to your PR template. A single checkbox item that asks whether the change warrants an ADR is sufficient to prompt the habit without creating friction.

3. Identify the three most painful undocumented decisions in your current codebase. Write retrospective ADRs for them this sprint. This builds team familiarity with the format while addressing immediate knowledge gaps.

4. Establish a clear threshold. Define, as a team, what kinds of decisions require an ADR. Write this threshold in the ADR directory's README.md so it is self-documenting.

5. Reference ADRs from code comments at decision boundaries. Wherever a significant architectural constraint manifests in code, add a comment linking to the relevant ADR. This makes the decision record discoverable from the place engineers most often encounter its effects.

The 80/20 Insight

If most of the value of ADRs comes from a small number of practices, they are these: write at decision time (not after), document alternatives genuinely considered, and store records where engineers actually work (version control, co-located with code). Teams that do these three things consistently - even with an imperfect format - capture the large majority of the practice's benefit. Everything else is refinement.

The temptation to build an elaborate ADR system before writing any ADRs is a known failure pattern. Start with a markdown file, a simple template, and the next architectural decision your team makes. The system will emerge from practice.

Conclusion

Architecture Decision Records are not documentation for documentation's sake. They are a form of asynchronous communication between the engineers who built a system and the engineers who will maintain, extend, and eventually replace it. The cost of writing an ADR is measured in minutes. The cost of not writing one is measured in hours of confused debugging, repeated architecture debates, and avoidable mistakes.

The practice does not require a new tool, a formal process, or organizational buy-in. It requires a directory, a template, and a team habit. The habit is the hard part - but it is also the only part that matters. Once a team experiences the first moment of "oh, the ADR explains exactly why this was done this way," the practice tends to sustain itself.

ADRs are a small investment in institutional memory that compounds over time. The system you are building today will be maintained for years, probably by people who have not yet joined your organization. Write the ADRs for them.

References

  1. Nygard, M. (2011). Documenting Architecture Decisions. Cognitect Blog. https://cognitect.com/blog/2011/11/15/documenting-architecture-decisions
  2. Richards, M., & Ford, N. (2020). Fundamentals of Software Architecture. O'Reilly Media. (Chapter 19 covers decision documentation practices.)
  3. Ford, N., Richards, M., Sadalage, P., & Dehghani, Z. (2021). Software Architecture: The Hard Parts. O'Reilly Media. https://www.thoughtworks.com/insights/books/software-architecture-hard-parts
  4. Pryce, N. adr-tools: Command-line tools for working with Architecture Decision Records. GitHub. https://github.com/npryce/adr-tools
  5. Architecture Decision Records - community hub, templates, and tooling index. https://adr.github.io/
  6. Keeling, M. (2017). Design It! From Programmer to Software Architect. Pragmatic Bookshelf. (Chapter 6 covers lightweight decision records.)
  7. Zimmermann, O. (2021). Architectural Decision Guidance Across Projects: Problem Space Modeling, Decision Backlog Management and Cloud Computing Knowledge. WICSA/ECSA Conference Proceedings. (Introduces the concept of decision backlog management in enterprise contexts.)
  8. Brown, S. (2018). Software Architecture for Developers (Volume 2: Visualise, document and explore your software architecture). Lean Publishing. (Covers ADRs in the context of the C4 model and lightweight documentation.)