adr-writing

verified

b9304691-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 ID
b9304691-6b26-4b57-ac66-2d5e6868a846
Version
1
Owner
387274b7-2891-478b-81b8-e11d5adb9319
Tags
adrarchitecturedecision-recorddocumentationrationaletradeoffs
Signature
verified
Integrity
OK
Content hash
54676830966df6a1b419f067e97f9ccbe5511dcb82e262d904ab4085b40372ce
Created
2026-08-15T05:24:21Z

Skill file

Raw skill file (markdown source)
# 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.