Architecture Decision Records
An ADR captures one architectural decision — context, forces, the choice, and consequences — so future engineers know WHY, not just what was decided. Lightweight, version-controlled, and superseded rather than deleted.
Six months after the B2B billing platform switched from a REST-based inter-module communication model to an event-driven one, a new engineer asked why. The team scoured Slack, Confluence, and pull requests for three days and found fragments: a thread that said “we discussed this in the March meeting,” a PR description that said “switching to events,” and a Jira ticket that said “technical debt.” Nobody could reconstruct the forces that drove the decision — the chatty REST calls causing cascading failures during billing runs, the three alternatives they considered, the reason they rejected a message queue in favor of domain events, or the tradeoffs they consciously accepted. When a failure mode appeared that the event-driven model couldn’t handle, the team had to debate the original decision all over again without the original context. Two engineers wanted to revert; two wanted to push forward. They had no record of what they had already considered.
An Architecture Decision Record would have taken thirty minutes to write. Instead, it cost three days of archaeology and an unresolved architectural argument.
What an ADR is and what it is not
An Architecture Decision Record is a short document — typically one to two pages — that records a single significant architectural decision. Michael Nygard proposed the format in 2011. The key insight is that the decision itself is less important than the reasoning: the context that made the decision necessary, the forces pulling in different directions, the alternatives considered, and the consequences accepted.
An ADR is not:
- A design document (those describe how something is built)
- A requirements document (those describe what it must do)
- A post-mortem (those describe what went wrong)
- A living document (an ADR is immutable once accepted; it is superseded, not edited)
The format Nygard proposed has five sections:
Title. A short imperative phrase: “Use domain events for inter-module communication” not “Event-driven architecture decision.”
Status. Proposed → Accepted → Superseded (by ADR-NNN) | Deprecated. Status is never “rejected” for decisions that were accepted — those are superseded when a newer decision overrides them. Rejected proposals exist as Proposed ADRs that were never accepted.
Context. The forces and circumstances that made this decision necessary. What was the problem? What constraints existed? What was the current situation?
Decision. The choice made, stated plainly. “We will use domain events published to an in-process event bus for all cross-module communication.”
Consequences. The tradeoffs accepted. What becomes easier? What becomes harder? What new risks are introduced?
Why the WHY is the value
Consider two records of the same decision:
Record A (commit message): “Switch inter-module communication to domain events.”
Record B (ADR): “Context: The billing module calls the ordering module’s REST API synchronously during invoice generation. When order volume spikes, this causes cascading timeouts — billing runs fail because ordering is slow, and the retry amplifies the load. We evaluated three options: (1) add a queue between REST callers, (2) switch to domain events on an in-process bus, (3) redesign billing to own a read-model of order data. We rejected (1) because it adds operational complexity (a broker to manage) without solving the coupling. We rejected (3) because billing owning order data violates data ownership boundaries (ADR-004). Decision: We adopt domain events on an in-process bus (no broker). Consequences: cross-module calls become asynchronous; billing logic must handle events arriving out of order; we accept that billing cannot immediately confirm an order’s current status — it works from the last event it received.”
Record B lets a future engineer understand: (a) why the problem existed, (b) what was considered and rejected and why, (c) what tradeoffs were consciously accepted. When a new problem appears — say, billing receiving events out of order — the team can see that out-of-order delivery was already known and accepted. They don’t re-debate the original decision; they work within the frame that was set.
Record A forces archaeology. Record B is institutional memory made explicit.
▸Why this works
Why does the ADR have to capture rejected alternatives? Because the alternatives that were rejected are what prevent re-litigation. Without a record of why option (1) was rejected, the next engineer who hits a problem will independently re-propose option (1). With the record, the discussion can start from “we rejected this for reason X — has X changed?” rather than from zero. The rejected path is as informative as the chosen one.
The billing platform team has an accepted ADR (ADR-007) that says 'use domain events for cross-module communication.' A year later, performance profiling shows that the in-process event bus creates a bottleneck when processing 50,000 concurrent orders. A senior engineer proposes replacing it with a Kafka-based broker. What is the correct way to handle this using ADRs?
ADRs as first-class artifacts: version control and location
The lightweight ADR format is specifically designed to live next to the code, version-controlled in the same repository. Nygard’s original proposal puts them in doc/arch/adr-NNN-title.md. ThoughtWorks Radar (2016) lists Lightweight Architecture Decision Records as a technique to adopt specifically because they are cheap and version-controlled.
The benefits of co-locating ADRs with code:
- Change history: when the code changes, the ADR history shows when and why the decision was made, correlatable with the commit that implemented it.
- Discoverability: engineers exploring the codebase find ADRs where they work (in the repo, not in a wiki that may be stale or inaccessible).
- Pull request workflow: an ADR can be proposed in a pull request, reviewed alongside the implementation change, and merged when the decision is accepted.
- Durability: wikis drift, Confluence pages go stale, Slack threads are archived. The ADR in the repo survives as long as the codebase does.
▸lesson.inset.note
The ADR format is deliberately minimal. Some teams add sections (Pros/Cons tables, risk ratings, links to RFC-style discussions). That is fine as long as the core information is present: context, decision, consequences. The Nygard format is a minimum, not a maximum. The failure mode is over-engineering the format until writing ADRs becomes so burdensome that nobody writes them. A two-paragraph ADR is infinitely better than no ADR.
A team stores architecture decisions in Confluence. An engineer argues that Confluence is better than ADRs in the repo because Confluence has rich formatting, search, and commenting. A senior engineer disagrees. What is the most important structural argument against Confluence for decision records?
What decisions warrant an ADR
Not every decision needs an ADR. The signal is: would a senior engineer joining the team in two years need to know why this decision was made, and could they not figure it out from the code?
Decisions that warrant an ADR:
- Architectural style choices (event-driven vs request-response, modular monolith vs microservices)
- Significant technology adoptions (choosing a specific event store, a message broker, an ORM)
- Rejected alternatives that are likely to be re-proposed (“we considered X and rejected it because…”)
- Decisions with non-obvious consequences or accepted tradeoffs
- Decisions that constrain future options (“we cannot change to X without re-implementing Y”)
Decisions that do not need an ADR:
- Routine implementation choices (naming conventions, file structure within a module)
- Framework-idiomatic patterns (using dependency injection because the framework uses it)
- Decisions that are obvious from the code and domain
The billing platform example: “Use PostgreSQL” probably doesn’t need an ADR if the team is a Rails shop and PostgreSQL is the default. “Use PostgreSQL event sourcing over a dedicated event store like EventStoreDB” absolutely needs an ADR, because there are non-obvious forces (operational familiarity with PG, the cost of running EventStoreDB in production, the GDPR deletion problem with event sourcing) that future engineers cannot infer from the code.
The B2B platform engineering lead reviews the team's ADR backlog and finds four candidate decisions. Which one most clearly warrants an ADR?
- 01What are the five sections of Nygard's ADR format, and why is the 'Context' section the most important?
- 02What is the ADR status lifecycle, and why are accepted ADRs never deleted or edited?
- 03Why should ADRs be stored in the code repository rather than a wiki or project management tool?
The billing platform’s three-day archaeology hunt — reconstructing a decision that took an hour to make — is the failure mode ADRs prevent. The decision log is not about bureaucracy; it is about institutional memory made explicit and durable.
The format is deliberately minimal: context, decision, consequences. The immutability is deliberate: accepted ADRs are superseded, not edited. The location is deliberate: next to the code in version control, where engineers work and where the record will outlive any wiki.
The most important part of an ADR is the rejected alternatives. Without them, every engineer who encounters the same problem will re-propose the same alternatives. With them, the team can start from the existing analysis and ask: “Has the context changed enough to revisit this?”
ADRs are the bridge between the architecture as it exists and the architecture as it came to be. Without them, every structural decision looks like it was inevitable. With them, engineers understand that every decision was a tradeoff — and can evaluate whether the forces that drove it still apply.
Practice
Start at the top. Tasks go easiest → hardest: recall a fact, apply it to a case, then a senior-level stretch. Open one, attempt it, then reveal.
Something unclear?
Ask a question about this lesson. Questions are anonymous and go straight to the author to make the lesson better.
Apply this
Put this lesson to work on a real build.