Architecture Decision Records: Making Technical Decisions Explicit

Every significant technical decision your team makes will be questioned later. When the original authors have left. When the context has changed. When someone proposes replacing the system. When a new engineer asks “why does this work like this?”

Most teams have no good answer to these questions. The reasons behind decisions live in people’s heads, in Slack threads that are impossible to find, or nowhere at all.

Architecture Decision Records are a lightweight practice that fixes this. They capture not just what was decided, but why — including what was considered and rejected. The investment is small. The return compounds over time.

What an ADR Is

An ADR is a short document — typically one to two pages — that records a significant architectural decision. The key word is significant: a decision that:

  • Has long-lasting consequences
  • Is difficult or expensive to reverse
  • Affects multiple people or systems
  • Represents a meaningful trade-off

Choosing between PostgreSQL and MySQL for a new service is probably ADR-worthy. Choosing which JSON serialization library to use probably isn’t, unless there’s a specific constraint that makes it non-obvious.

ADRs are immutable once accepted. If the decision changes, you write a new ADR that supersedes the old one. The historical record is preserved.

The Template That Works

Many ADR templates exist. This one focuses on what actually matters:

# ADR-0042: Use PostgreSQL as the primary datastore for the Orders service

**Date**: 2024-11-08  
**Status**: Accepted  
**Deciders**: [Engineering lead, Backend team]  
**Supersedes**: N/A  

## Context

The Orders service needs a primary datastore. We're building a new service
from scratch with a 6-week delivery deadline. The data model is relational:
orders contain order lines, which reference products and customers.

We have existing expertise in PostgreSQL and MySQL across the team. 
The team has no production experience with NoSQL stores.

Expected load: 50 write operations/second at peak, 500 read operations/second.
Data volume: ~10M orders over the next 2 years.

## Decision

We will use PostgreSQL 16 as the primary datastore.

## Options Considered

### Option A: PostgreSQL (chosen)
**Pros**: Team expertise, ACID transactions, strong JSON support for variable 
metadata, excellent tooling, proven at this scale, hosted options on AWS (RDS/Aurora).  
**Cons**: Schema migrations require coordination, horizontal write scaling 
requires Citus or read replicas.

### Option B: MySQL (InnoDB)
**Pros**: Team has some experience, similar performance characteristics.  
**Cons**: Less capable JSON support, historically weaker transaction isolation 
defaults. No meaningful advantage over PostgreSQL for this use case.

### Option C: MongoDB
**Pros**: Schema flexibility, document model might fit order structure.  
**Cons**: No team expertise, weaker transaction support (multi-document 
transactions are available but complex), operational complexity we cannot 
absorb in the current timeline.

### Option D: Amazon DynamoDB
**Pros**: Managed, scales horizontally.  
**Cons**: No team expertise, requires upfront access pattern design that we 
cannot finalize this early, eventual consistency model complicates order state 
management, higher operational complexity for complex queries.

## Consequences

**Positive:**
- Fast to implement — team knows PostgreSQL well
- ACID transactions simplify order state management
- Strong tooling for migrations (Flyway), ORM (Spring Data JPA), monitoring

**Negative:**
- We accept vertical scaling constraints — acceptable for 2-year horizon
- Schema changes require Flyway migrations with backward-compatibility discipline
- Single point of failure without read replicas (planned for production)

## Notes

This decision assumes the load estimates are accurate ±10×. If orders volume
exceeds 100M or write throughput exceeds 500/sec, this decision should be revisited.
A read replica should be provisioned before the service reaches 5M orders.

A few things about this template:

Quantify the context. Vague context produces vague decisions. “Expected high load” is useless. “50 writes/second at peak” gives the next team member something to evaluate against current load.

The options section is the most valuable part. The decision alone tells you what was chosen. The options tell you what was considered and why those paths were rejected. This is where the institutional knowledge lives.

Include explicit consequences, positive and negative. Every decision has tradeoffs. Documenting the negative consequences acknowledges them and makes them visible to future reviewers.

Include conditions for revisiting. “If orders volume exceeds 100M” is a concrete trigger for re-evaluation. This is more useful than leaving the decision as an eternal axiom.

Why Rejected Alternatives Matter

Most ADR guides focus on the chosen decision. The rejected alternatives are often more valuable.

Consider this scenario: a new engineer joins the team and proposes switching from PostgreSQL to MongoDB because “it handles JSON better and is more scalable.” Without ADR-0042, this triggers a full re-evaluation from scratch. The team has to reconstruct the reasoning from memory. If the original decision-makers have left, the knowledge may be gone entirely.

With ADR-0042, the answer is: “We evaluated MongoDB in November 2024 and rejected it because of team expertise, transaction requirements, and timeline constraints. Have those constraints changed? If so, let’s write a new ADR.”

This is the right conversation to have. It’s faster, more focused, and rooted in actual context rather than abstract preference.

Rejected alternatives also serve as an antipattern catalogue. If you evaluated EventSourcing for the orders service and decided it was too complex for the problem, that rejection saves the next team from suggesting it again without a compelling reason.

ADR Status Lifecycle

ADRs move through statuses:

  • Proposed: under discussion
  • Accepted: decision made and active
  • Superseded by ADR-N: a newer decision replaces this one
  • Deprecated: no longer relevant (service retired, context changed)

When a decision changes, do not modify the old ADR. Write a new one. Reference the old one explicitly:

# ADR-0078: Migrate Orders service from PostgreSQL to CockroachDB

**Status**: Accepted  
**Supersedes**: ADR-0042 (PostgreSQL for Orders service)

## Context

Since ADR-0042 was accepted in November 2024, the Orders service has 
grown to 250M orders with peak write throughput of 800/second. PostgreSQL 
with read replicas is approaching its scalability limits...

The chain of ADRs tells the story of the system’s evolution. That history has real value when you’re trying to understand why a system looks the way it does.

Where to Store ADRs

ADRs belong in the repository, next to the code. The standard location is docs/adr/ with numbered files:

docs/
└── adr/
    ├── 0001-use-hexagonal-architecture.md
    ├── 0002-use-kafka-for-event-streaming.md
    ├── 0003-postgresql-for-orders-service.md
    └── README.md  ← index of all ADRs

Storing ADRs with code means:

  • They’re versioned alongside the code they describe
  • PRs can include both the code change and the ADR in one review
  • They’re visible to anyone cloning the repository
  • They don’t require access to a separate wiki or document system

Tools like adr-tools (CLI) or log4brains (visualization) can help manage them, but they’re optional. A plain markdown file in the repository is sufficient.

When to Write an ADR

The hardest part is building the habit. Some triggers:

  • Choosing a framework, database, or messaging system
  • Defining the module structure of a new service
  • Choosing a communication pattern (sync vs async, event-driven vs REST)
  • Deciding how to handle authentication or authorization
  • Making a significant trade-off (consistency vs availability, simplicity vs flexibility)
  • Choosing to not do something that might seem obvious (e.g., deciding not to use microservices)

When in doubt, write it. The cost of writing an ADR for a decision that doesn’t need one is 20 minutes. The cost of not writing one for a decision that does is compounded over years.

The Org-Level Pattern

For teams with multiple services, maintain an ADR index at the organization level for cross-cutting decisions, and service-level ADRs for service-specific ones.

Cross-cutting decisions worth documenting at the org level:

  • Authentication and authorization patterns
  • Inter-service communication standards
  • Observability stack (what tools, what to instrument)
  • Deployment and CI/CD standards
  • Data governance patterns

Service-level decisions stay in the service’s repository.

Start Small

You don’t need to retroactively document every past decision. Start from today. Pick the next significant decision your team makes and write an ADR for it. Do it again for the one after. After six months, you’ll have a record of your team’s reasoning that will prove its value repeatedly.

The teams that maintain ADRs consistently report the same thing: the discipline of writing the options and consequences section forces clearer thinking before the decision is made, not just documentation after. That’s the real value — ADRs make decisions better by making the reasoning explicit.

adrarchitecturedocumentationdecision-makingengineering-process
← All articles