Skip to content
ExponaBLOG
ENGINEERING CRAFT · DAY 050 / 100

Architecture decision records: best practices explained

Write down why, so nobody has to guess later.

Expona Admin7 min read

Opening: Why decisions deserve a record

Six months into a project, you find a comment in the code: "We can't use DynamoDB here because of something about transactions." Nobody remembers why. The person who decided is on leave. You spend a day re-researching a choice that was already made. This is the problem that architecture decision records solve. Architecture decision records: best practices explained is about creating a simple paper trail so your future self, and everyone who joins the team, knows not just what you built, but why you built it that way.

What an ADR is: A short document recording one significant decision

An Architecture Decision Record (ADR) is a brief, structured document that captures a single significant choice made during the design or evolution of your system. It is not a full design document, a request for comment (RFC), or a change log. It is narrower: it records decisions that have consequences lasting weeks or months—things like choosing a database, switching to a new messaging system, or deciding to split a monolith.

The key word is significant. Not every decision needs an ADR. Choosing a specific npm package for date formatting does not. But choosing whether to use PostgreSQL or DynamoDB for your primary data store does. So does deciding to move authentication into a separate microservice, or to adopt a particular API versioning strategy.

An ADR typically runs 200–500 words. It should take five minutes to read and two minutes to understand. If you need to explain it with diagrams or hours of context, it is probably the wrong tool; you need a design document alongside it. But for the quick, weighty decision that shapes your architecture, an ADR is exactly right.

A simple template: Context, decision, alternatives and consequences

There are several ADR formats in the wild. We recommend a lean template with four core sections: context, decision, alternatives, and consequences. Here is a concrete example based on a real choice many teams face:

typescript

# ADR 012: Use PostgreSQL for CRM data

**Status:** Accepted

**Date:** 2025-01-15

## Context

Our CRM application stores customer profiles, orders, and interactions. 
We need to support complex queries: finding customers who bought X in the 
last 30 days and also interacted via email. Reporting requirements are 
heavy. The team has strong SQL experience. Data volume is ~500 GB today, 
expected to grow to 2 TB over three years.

## Decision

We will use PostgreSQL 16 on AWS RDS (Multi-AZ, 2 vCPU initially, 
autoscaling disabled). Connection pooling via PgBouncer. We will version 
the schema using Flyway, with migrations in the monorepo.

## Alternatives Considered

- **MongoDB**: Rich queries, horizontal scaling. Rejected because 
  schema flexibility adds complexity for a domain we understand well. 
  No native transactions across collections; joins are painful.

- **DynamoDB**: Serverless, good for spiky load. Rejected because 
  query patterns (especially reports) need relational semantics. 
  No SQL means custom query logic in application code.

- **CockroachDB**: Distributed SQL, Postgres-compatible. Rejected 
  because operational complexity is too high for a 3-person team. 
  License cost also factors in.

## Consequences

- Positive: Strong ACID semantics, familiar SQL dialect, mature 
  ecosystem, good monitoring tools.
- Positive: Joins are fast and natural; reporting queries are readable.
- Positive: Vertical scaling is straightforward; RDS handles backups.
- Negative: Horizontal scaling requires sharding logic; we own that cost.
- Negative: Schema migrations require planning; downtime is possible 
  without careful sequencing.
- Negative: We must manage connection pools and understand index design.

## Notes

Revisit this decision if query latency exceeds 200ms at p99, or if 
we need to scale to >10 TB. Reassess in Q3 2025.

Notice what this does: it explains the constraint (the reporting need, the team skill set), it lays out real alternatives with honest reasons for rejection, and it lists both the wins and the costs. Someone reading this six months later does not have to guess. They know the trade-off you made.

Keep them in the repo: Decisions live next to the code they affect

An ADR has almost no value if it sits in a wiki, a Notion workspace, or an email archive that nobody reads. Store ADRs in your version control system, alongside your code. Most teams create a directory at the root: docs/adr/ or architecture/decisions/. Each ADR gets its own markdown file, numbered sequentially: adr-001.md, adr-002.md, and so on.

Why the repo? First, it appears in code review. If a pull request changes how authentication works, a reviewer naturally asks: "Is there an ADR for this?" Second, it travels with the code. When you clone the repo on day one, you get the full decision history. Third, it is under version control, so you can blame or log it. Fourth, it integrates into your CI/CD pipeline—you can lint it, auto-generate a list of decisions, or block merges if the ADR template is incomplete.

Store them as plaintext markdown, not Word documents or PDFs. Markdown is diff-able, merge-able, and reads well on GitHub or any other platform. If you have diagrams, reference them as images in the same directory.

Never edit history: Supersede an ADR with a new one when decisions change

The most common mistake is to update an old ADR when the decision changes. Do not do this. Treat ADRs as immutable records. If you decide to switch from PostgreSQL to DynamoDB, do not edit ADR 012. Instead, create ADR 023: "Supersede ADR 012: Migrate CRM data to DynamoDB," explain the new constraints (perhaps load has exploded, or query patterns shifted), record the alternatives you considered this time, and state the consequences of the switch.

This preserves the decision history. Future engineers can read ADR 012 and understand what you were thinking in 2025. They can read ADR 023 and see what changed. If the decision flips again in 2026, they see the full arc. You also avoid the Schrödinger's ADR problem: a document that is partly outdated and partly current, leaving readers confused.

Mark old ADRs as "Superseded by ADR 023" in their status field. Link to the superseding ADR. The original stays in the repo, unchanged.

Common mistakes

  • Writing too much: An ADR is not a design document. If you are writing more than 800 words, you are probably mixing in implementation detail or regulatory background that belongs elsewhere. Keep it tight. Link to related docs if needed.
  • Vague consequences: "It will scale better" or "It is easier to maintain" are not consequences; they are opinions. Be specific: "We eliminate the need to manage shards, reducing operational burden from 2 hours per week to 0, but we accept a per-query latency increase of 10–20 ms for analytical queries."
  • Forgetting the context: A decision only makes sense in context. Why did you consider DynamoDB in the first place? Because load was spiking? Because the team wanted to try a new tool? Spell it out. Otherwise, the ADR is unmoored.
  • Treating ADRs as RFCs: An ADR records a decision that has been made. If you are still deliberating, write a Request for Comments (RFC) or an RFD (Request for Discussion). Do not publish an ADR until the decision is final and stakeholders have agreed.
  • Letting them rot in a wiki: If ADRs live anywhere but the repo, they will be ignored and become stale. Store them in version control, full stop.
  • Ignoring ADRs in code review: Reviewers should ask: "Does this change break any existing ADR?" If it does, either the change is wrong or the ADR needs superseding. Make it part of your checklist.

Key takeaways

  • An ADR is a short, structured record of one significant architectural decision, capturing context, the choice made, alternatives considered, and consequences.
  • Use a simple template: status, context, decision, alternatives, consequences. Keep it under 500 words.
  • Store ADRs in your repository under docs/adr/, as plain markdown files, numbered sequentially. They travel with the code and appear in code review.
  • Never edit an old ADR. When a decision changes, create a new ADR that supersedes it, preserving the full history.
  • Consequences should be concrete, not vague. List both wins and costs. Be honest about trade-offs.
  • Not every decision needs an ADR—only the ones that shape the system for weeks or months. Use good judgment.
  • Make ADR review part of your code review process. Ask: "Is there an ADR for this decision?" If not, should there be?
ShareXLinkedInReddit

Found this useful? Support the writing.

Buy me a coffee
Written by
Expona Admin
Site administrator

Comments

Loading comments…

Leave a comment

Comments are reviewed before they appear. Be kind and stay on topic.

Related articles