The Software Architecture Decision Record (ADR) Guide
Last year, a new engineer joined our team and asked a simple question: "Why does this service use MongoDB instead of PostgreSQL?" Nobody on the team knew. The engineer who made that decision had left two years ago. There was no documentation, no Slack thread, no commit message that explained the reasoning. We spent three days researching the trade-offs before concluding that the original decision was actually wrong for our current use case — but we had no way to know that without re-discovering all the constraints the original author considered.
Architecture Decision Records (ADRs) prevent exactly this scenario. They're lightweight documents that capture the context, decision, and consequences of significant architectural choices. This guide covers everything: what ADRs are, when to write them, how to write them well, and how to build a team culture around them.
What Is an Architecture Decision Record?
An ADR is a short document (typically 1-2 pages) that captures a single architectural decision along with its context and consequences. The concept was introduced by Michael Nygard in 2011 and has since become a standard practice in software teams worldwide.
The key insight: the decision itself is less important than the context that led to it. A year from now, anyone reading the ADR should understand not just what was decided, but why — and what alternatives were considered and rejected.
| Component | Purpose | Example |
|---|---|---|
| Title | Short, descriptive name with a number | ADR-007: Use PostgreSQL for job listing storage |
| Status | Current lifecycle stage | Proposed → Accepted → Deprecated → Superseded |
| Context | What problem/situation prompted this decision | We need to store 50K+ job listings with full-text search |
| Decision | What was decided | We will use PostgreSQL with pg_trgm extension |
| Alternatives | What else was considered | MongoDB, Elasticsearch, SQLite |
| Consequences | What changes as a result | Team needs PostgreSQL expertise; we accept limitations of pg_trgm vs Elasticsearch |
According to the ThoughtWorks Technology Radar, ADRs have been in the "Adopt" ring since 2016 — their highest recommendation level. In 2024, they report that "ADRs have become the de facto standard for architecture documentation in teams practicing evolutionary architecture."
The ADR Template
Here's the template I use, based on Nygard's original with additions from practical experience:
# ADR-NNN: [Decision Title]
## Status
[Proposed | Accepted | Deprecated | Superseded by ADR-XXX]
## Date
[YYYY-MM-DD]
## Authors
[Who proposed this decision]
## Context
[What is the issue or problem that motivates this decision?
What are the forces at play — technical, business, organizational?
What constraints exist?]
## Decision
[What is the change that we're proposing or decided upon?
State the decision clearly and concisely.]
## Alternatives Considered
### Alternative 1: [Name]
- **Pros:** [What's good about this option]
- **Cons:** [What's bad about this option]
- **Why rejected:** [Specific reason]
### Alternative 2: [Name]
- **Pros:** [...]
- **Cons:** [...]
- **Why rejected:** [...]
## Consequences
### Positive
- [What good things result from this decision]
### Negative
- [What bad things result — every decision has trade-offs]
### Neutral
- [What changes that aren't clearly positive or negative]
## Related Decisions
- [ADR-XXX: Related previous decision]
## Notes
[Additional context, links to research, benchmark results, etc.]
Real-World ADR Examples
Example 1: Choosing a Database
# ADR-003: Use PostgreSQL with Prisma ORM for Primary Data Storage
## Status
Accepted
## Date
2024-01-15
## Authors
Ismat, Backend Team
## Context
BirJob scrapes 80+ job listing sources daily, producing approximately
15,000 job listings per run. We need a database that supports:
- Full-text search in Azerbaijani and English
- Efficient deduplication by URL
- Complex queries for job filtering (location, category, company, date)
- Reliable transactions for the payment system
- Integration with our Next.js/TypeScript stack
Our team has 1 backend developer. Operational simplicity is critical.
We're deployed on Vercel + Supabase.
## Decision
We will use PostgreSQL (via Supabase) with Prisma ORM.
## Alternatives Considered
### Alternative 1: MongoDB
- **Pros:** Flexible schema (useful when scraper outputs vary),
good for document-shaped data, MongoDB Atlas free tier
- **Cons:** No native full-text search for Azerbaijani, weaker
transaction support, Prisma MongoDB support is less mature
- **Why rejected:** Our data is relational (jobs belong to companies,
companies have locations, users save jobs). Forcing this into
documents creates duplication and consistency problems.
### Alternative 2: Elasticsearch + PostgreSQL
- **Pros:** Superior full-text search, handles Azerbaijani text well,
excellent for faceted search
- **Cons:** Two systems to maintain, higher infrastructure cost
(~$50/month for managed ES), sync complexity between PG and ES
- **Why rejected:** At our current scale (50K listings), PostgreSQL's
built-in text search (pg_trgm + to_tsvector) is sufficient.
We can add Elasticsearch later if search demands grow.
### Alternative 3: SQLite
- **Pros:** Zero infrastructure, embedded, very fast for reads
- **Cons:** Single writer, no concurrent access from multiple
serverless functions, no hosted solution for Vercel
- **Why rejected:** Serverless deployment requires a networked
database. SQLite doesn't support this without tools like Turso
or LiteFS, adding complexity.
## Consequences
### Positive
- Single database for all data (jobs, users, payments) —
simple operations
- Prisma gives us type-safe queries and automatic migrations
- Supabase provides managed PostgreSQL with free tier covering
our current needs
- Team already knows SQL — minimal learning curve
### Negative
- Full-text search quality is lower than Elasticsearch
(acceptable at current scale)
- Supabase free tier has connection limits (may need upgrade
at ~100K concurrent users)
- Prisma adds a layer between us and raw SQL (sometimes
frustrating for complex queries)
### Neutral
- We'll use Prisma Migrate for schema changes
- Supabase handles backups, but we'll add our own pg_dump
for critical data
## Notes
- Benchmark: pg_trgm search on 50K rows: 12ms average
- Supabase free tier: 500MB storage, 2 direct connections
- If search quality becomes insufficient, migration path is
clear: add Elasticsearch as a read-only search index
Example 2: Choosing an Authentication Strategy
# ADR-011: Use NextAuth.js with Email Magic Links for Authentication
## Status
Accepted
## Date
2024-03-20
## Authors
Ismat
## Context
BirJob needs authentication for two user types:
1. Job seekers: save jobs, set alerts, track applications
2. HR/Employers: post jobs, manage listings, access analytics
Requirements:
- Low friction signup (Azerbaijan market — users expect simplicity)
- No password management liability
- Support for email + potentially Google OAuth
- Session management compatible with Next.js App Router
## Decision
We will use NextAuth.js (Auth.js) with email magic links as the
primary authentication method, with Google OAuth as a secondary option.
## Alternatives Considered
### Alternative 1: Username + Password (custom)
- **Pros:** Users are familiar with it, full control
- **Cons:** Password storage liability, reset flow complexity,
users forget passwords, phishing risk
- **Why rejected:** Password-based auth creates more support
tickets than any other feature. Magic links eliminate this.
### Alternative 2: Clerk
- **Pros:** Beautiful UI, handles everything, great DX
- **Cons:** $25/month after 10K MAUs, vendor lock-in, limited
customization of flow for Azerbaijani language
- **Why rejected:** Cost becomes significant at scale, and we
need Azerbaijani language support in auth flows.
### Alternative 3: Supabase Auth
- **Pros:** Integrated with our database, free tier generous
- **Cons:** Less flexible with Next.js App Router, fewer
providers, RLS-based auth is complex
- **Why rejected:** NextAuth has better Next.js integration
and more community resources for edge cases.
## Consequences
### Positive
- Zero password management: no password storage, no resets,
no "forgot password" support tickets
- Users authenticate via email link — extremely low friction
- NextAuth integrates natively with Next.js middleware
### Negative
- Users dependent on email access (problem if email is down)
- Magic links can end up in spam folders
- No offline authentication capability
## Notes
- Magic link expiry: 10 minutes
- Session duration: 30 days (with sliding window)
- Rate limit: max 5 magic link requests per email per hour
When to Write an ADR
Not every decision needs an ADR. Here's a framework:
| Write an ADR When... | Don't Write an ADR When... |
|---|---|
| Choosing a database, framework, or major library | Choosing between two utility libraries (lodash vs ramda) |
| Designing a service boundary or API contract | Naming a variable or function |
| Deciding on an authentication/authorization strategy | Choosing a code formatting style (use Prettier) |
| Picking a deployment strategy or infrastructure | Deciding which test runner to use (usually obvious) |
| Making a trade-off that future developers will question | Making a decision that can be easily changed later |
| Deciding NOT to do something (e.g., why we don't use microservices) | Trivial implementation choices |
The litmus test: If a new team member would ask "why did we do it this way?" within their first month, it deserves an ADR.
Where to Store ADRs
| Location | Pros | Cons | Best For |
|---|---|---|---|
| In the codebase (docs/adr/) | Version controlled, reviewed in PRs, close to code | Non-developers can't easily access | Engineering-only decisions |
| Wiki (Confluence, Notion) | Easy editing, cross-team visibility, rich formatting | Gets stale, not version controlled, separate from code | Cross-functional decisions |
| GitHub Issues/Discussions | Built-in discussion, linked to PRs, searchable | Can get buried in issue noise | Open source projects |
My recommendation: Store ADRs in the codebase as Markdown files in docs/adr/. They're version-controlled, they appear in PRs for review, and they can't get out of sync with the code they describe. Use a numbering convention: 0001-use-postgresql.md, 0002-adopt-nextjs-app-router.md.
project/
├── docs/
│ └── adr/
│ ├── 0001-use-postgresql.md
│ ├── 0002-adopt-nextjs-app-router.md
│ ├── 0003-email-magic-links-auth.md
│ ├── 0004-prisma-orm.md
│ ├── 0005-vercel-deployment.md
│ └── template.md
├── src/
├── prisma/
└── package.json
ADR Lifecycle Management
Proposed → Accepted → [Active for months/years] → Deprecated or Superseded
│ │ │
│ └── Rejected (with reason) │
│ │
└── ADR-NNN supersedes this (link to new ADR) ────────┘
Key lifecycle rules:
- Never delete an ADR. Even deprecated decisions have historical value. Mark them as "Deprecated" or "Superseded by ADR-NNN."
- Never modify the Context or Decision of an accepted ADR. If the decision changes, write a new ADR that supersedes the old one. This preserves the historical record.
- Review ADRs quarterly. During architecture reviews, scan the ADR list. Are any decisions outdated? Do any need superseding?
Common ADR Mistakes
| Mistake | Why It's a Problem | Fix |
|---|---|---|
| Too long (10+ pages) | Nobody reads it, defeats the purpose | Limit to 1-2 pages. Move research to appendix. |
| No alternatives listed | Looks like a rubber stamp, not a decision | Always list 2-3 alternatives with honest pros/cons |
| Only positive consequences | Every decision has trade-offs — dishonest record | Force yourself to list negatives. They always exist. |
| Written after the fact | Post-hoc rationalization, not genuine decision-making | Write the ADR before implementing. Use it as the discussion tool. |
| Too many ADRs | ADR fatigue — team stops writing them | Apply the litmus test: would a new hire ask "why?" |
| ADRs without context | Future readers can't evaluate if decision still applies | Context section should include constraints and forces |
Opinionated: Why ADRs Are the Highest-ROI Documentation
1. ADRs are the only documentation I've seen consistently maintained. README files get stale. Wiki pages are abandoned. API docs drift from reality. But ADRs survive because they're written once and never need updating (you write a new one instead). This immutability is what makes them sustainable.
2. ADRs prevent the "second-system effect." When a new team member sees a "bad" decision and wants to rewrite everything, the ADR explains why the decision was made. Often, the original constraints still apply, and the rewrite is unnecessary. ADRs save teams from repeating mistakes.
3. ADRs improve decision quality. The act of writing down alternatives and consequences forces rigorous thinking. I've changed my mind about a decision while writing the ADR at least a dozen times. The format itself is a decision-making tool.
4. One ADR is worth more than zero ADRs. Don't try to document every past decision. Start with the next decision. Over time, you'll build a meaningful record. The perfect is the enemy of the good.
5. ADRs should be reviewed in PRs. When someone proposes a new ADR, it should go through the same review process as code. This ensures the team agrees on the decision and catches missing context or unconsidered alternatives.
Action Plan: Introducing ADRs to Your Team
Day 1: Setup
- Create
docs/adr/in your repository - Add the template file as
docs/adr/template.md - Write your first ADR about a recent decision (even retroactively)
- Share it with the team as an example
Week 1: First Real ADR
- When the next architectural decision comes up, write a "Proposed" ADR
- Submit it as a PR for team review
- Discuss alternatives in the PR comments
- Merge as "Accepted" when the team agrees
Month 1: Build the Habit
- Add "Does this need an ADR?" to your PR template checklist
- Reference ADRs in code comments when relevant
- Review existing ADRs during sprint planning
- Celebrate the first time an ADR prevents a redundant discussion
Ongoing: Maintain and Evolve
- Quarterly: review the ADR list for outdated decisions
- When superseding an ADR, cross-reference both documents
- Include ADR numbers in commit messages for related changes
- Onboard new team members by having them read all ADRs (it's the fastest way to understand the system's architecture)
Conclusion: Decisions Are Your Most Valuable Artifact
Code can be rewritten. Tests can be regenerated. But the reasoning behind architectural decisions — the constraints, trade-offs, and rejected alternatives — is knowledge that exists only in people's heads until someone writes it down. When those people leave, that knowledge leaves with them.
ADRs are cheap to write (30 minutes), expensive to not write (days of re-discovery), and valuable forever. Start today. Write one ADR about the last significant decision your team made. You'll never regret it.
Sources
- Michael Nygard — Documenting Architecture Decisions (2011)
- ThoughtWorks Technology Radar — ADRs
- ADR GitHub Organization — Tools and Resources
- Joel Parker Henderson — ADR Collection
- Design It! by Michael Keeling (Pragmatic Bookshelf)
- AWS — Architecture Decision Records Guidance
I'm Ismat, and I build BirJob — Azerbaijan's job aggregator scraping 80+ sources daily.
