adr-writing
verifiedb9304691-6b26-4b57-ac66-2d5e6868a846
Record architecture decisions with context, options considered, and rationale so future readers know *why*. Use for any non-obvious or reversible-but-costly decision.
Metadata
Skill file
# Architecture Decision Records (ADRs)
Use when making a significant architectural decision — one where there are at least two plausible options and the choice has long-term consequences.
## When to Write an ADR
The **"two plausible options" test**: If a reasonable engineer could argue for a different choice, write an ADR.
| Write an ADR | Skip (too trivial) |
|---|---|
| Postgres JSONB vs normalized tables | Tabs vs spaces (already decided by linter) |
| REST vs GraphQL for the public API | Which logger library (defer to existing convention) |
| Monolith vs microservices | Variable naming style |
| Event sourcing vs CRUD for orders | Linter configuration (ADR if introducing a new linter) |
| On-prem vs cloud deployment | File naming convention |
## The ADR Template
```markdown
# ADR-001: Use PostgreSQL JSONB for Audit Logs
**Status:** Accepted
**Date:** 2025-01-15
**Deciders:** @alice, @bob
## Context
We need to store audit logs for user actions. Each log entry has a fixed
set of fields (user_id, action, timestamp) plus variable action-specific
metadata (e.g., "changed_fields" for updates, "ip_address" for logins).
Two approaches were considered:
1. Normalized tables — separate tables per action type with strict schemas.
2. PostgreSQL JSONB — fixed columns for common fields, JSONB for metadata.
The system generates ~10K audit events/day and queries are primarily
"show all actions by user X in date range Y."
## Decision
Use PostgreSQL JSONB for action metadata with fixed columns for
user_id, action, and timestamp.
## Alternatives Considered
### Option A: Normalized Tables
- **Pros:** Strong schema enforcement, efficient indexed queries on any field.
- **Cons:** Schema migrations for every new action type; complex UNION queries
for "show all user actions"; ORM complexity.
- **Why rejected:** Migration overhead for 20+ action types outweighs the
query benefits for our access patterns.
### Option B: Dedicated Audit Service (e.g., Elasticsearch)
- **Pros:** Purpose-built for log queries, full-text search on metadata.
- **Cons:** New infrastructure dependency; eventual consistency challenges;
overkill for 10K events/day.
- **Why rejected:** Added operational complexity without sufficient volume
to justify it.
## Consequences
- **Positive:** Zero-migration addition of new action types; simple queries
for our primary access pattern.
- **Negative:** Cannot enforce schema on metadata at the DB level (mitigated
by application-level Pydantic validation).
- **Risks:** If audit query patterns shift to filtering by metadata fields,
we may need GIN indexes or a migration to normalized tables.
```
## File Naming and Storage
```bash
mkdir -p docs/adr
# Naming: ADR-<sequential number>-<kebab-case-title>.md
# Example: docs/adr/ADR-001-postgres-jsonb-audit-logs.md
# docs/adr/ADR-002-rest-api-over-graphql.md
```
### ADR index file (`docs/adr/README.md`)
```markdown
# Architecture Decision Records
| ADR | Title | Status |
|---|---|---|
| [ADR-001](ADR-001-postgres-jsonb-audit-logs.md) | PostgreSQL JSONB for Audit Logs | Accepted |
| [ADR-002](ADR-002-rest-api-over-graphql.md) | REST API over GraphQL | Accepted |
| [ADR-003](ADR-003-orm-choice.md) | SQLAlchemy over Django ORM | Superseded by ADR-005 |
```
## Status Values
| Status | Meaning |
|---|---|
| **Proposed** | Written but not yet decided |
| **Accepted** | Approved and in effect |
| **Deprecated** | Was accepted but no longer applies |
| **Superseded** | Replaced by a newer ADR (link to it) |
## Guardrails
- **Never** write an ADR for a decision that only has one reasonable option.
- **Never** skip documenting the *rejected alternatives* — the "why not" is the most valuable part for future readers.
- **Always** include concrete pros/cons, not vague "better performance" claims.
- **Always** list consequences including *negative* consequences — no decision is pure upside.
## Pitfalls
- **Writing ADRs for trivial choices**: "ADR-042: We will use 4-space indentation." This creates noise that buries real decisions. The "two plausible options" test prevents this.
- **Documenting the decision without the rejected alternatives**: Future readers see "we chose X" but don't know why Y and Z were rejected. They may rediscover Y and waste time re-evaluating.
- **ADRs that never get updated**: An ADR marked "Proposed" from 2023. Either decide, accept, or close it.
- **Overly long ADRs**: An ADR should be 1–2 pages. If it's 10 pages, you're writing a design doc, not an ADR.
- **Missing consequences**: "We chose X. The end." Every decision has tradeoffs — document them honestly.
## Verify / Checklist
- [ ] ADR passes the "two plausible options" test
- [ ] Status is clearly marked (Proposed / Accepted / Deprecated / Superseded)
- [ ] Context explains *why* a decision was needed (not just what was decided)
- [ ] At least one rejected alternative is documented with pros, cons, and rejection reason
- [ ] Consequences include both positive and negative outcomes
- [ ] ADR is stored in `docs/adr/` with sequential numbering
- [ ] Index README lists all ADRs with status
- [ ] Superseded ADRs link to their replacement
Attached files
No attached files.