Skip to content
My Guidelines
Guides
In this guide (17 items)

Architectural Decision Records

Architectural Decision Records (ADR)

BINDING RULES FOR ALL AI AGENTS: Every architecturally significant decision MUST be captured as an ADR before merging. Default template = MADR. Default location = docs/adr/. ADRs are append-only: an accepted ADR is never edited — supersede it instead. Source: https://adr.github.io/.

This guide is the source of truth for ADR practice in this project. Skills and commands reference it; do not duplicate content elsewhere.

1. Definitions

  • Architectural Decision (AD): justified design choice addressing a functionally or non-functionally significant requirement.
  • Architecturally Significant Requirement (ASR): requirement with measurable effect on system architecture or quality attributes.
  • Architectural Decision Record (ADR): persistent document capturing one AD plus its context, alternatives, rationale, and consequences.
  • Decision Log: the ordered set of all ADRs in the repo.

2. When to Write an ADR

Write an ADR when the decision affects any of:

  • Layer boundaries or dependency direction (hexagonal violations).
  • Port shape or contract (inbound/outbound interfaces).
  • Adapter technology choice (JDBC vs JPA, Kafka vs RabbitMQ, REST vs gRPC).
  • Aggregate boundary or bounded context split.
  • Transaction strategy (Transaction port impl, outbox vs sync, saga vs 2PC).
  • Persistence engine, message broker, cache provider.
  • Security model (auth, authorization, multi-tenancy).
  • Cross-cutting concerns implemented as decorators (CoR ordering).
  • Public API contracts and versioning policy.
  • Build / deployment topology (modules, packaging, runtime).

Do NOT ADR trivial choices: variable names, internal helper signatures, formatting, single-file refactors with no API change.

3. Template Selection

Template Use when
MADR Default. Most decisions. Need tradeoff analysis with options + rationale.
Nygard Quick capture of an obvious choice with simple consequences.
Y-Statement One-line decision summary embedded in code comments or PR descriptions.
ISO 42010 Formal enterprise / regulated contexts only.

Default to MADR unless a project rule says otherwise.

4. MADR Template (default)

Filename: docs/adr/NNNN-title-with-dashes.md (e.g. 0007-use-kafka-outbox.md). NNNN is consecutive, zero-padded to 4 digits. Lowercase title with dashes.

---
status: proposed # proposed | accepted | rejected | deprecated | superseded
date: 2026-05-05 # YYYY-MM-DD, last update
decision-makers: [andres, jane]
consulted: [security-team] # two-way communication
informed: [product] # one-way communication
---
# NNNN. Short Decision Title
## Context and Problem Statement
Two or three sentences (or a short narrative) describing the forces and the
problem. State the ASR being addressed and any quality attributes at stake.
## Decision Drivers
- Driver 1 (e.g., must support exactly-once delivery)
- Driver 2 (e.g., team has Kafka operational experience)
- Driver 3 (e.g., budget caps managed-broker spend)
## Considered Options
- Option A — Kafka with transactional outbox
- Option B — RabbitMQ with publisher confirms
- Option C — Direct JDBC + Debezium CDC
## Decision Outcome
Chosen option: **Option A — Kafka with transactional outbox**, because it
satisfies the exactly-once driver while reusing existing operational
expertise.
### Consequences
- **Good** — exactly-once via outbox pattern + idempotent consumers.
- **Good** — reuse of existing Kafka cluster, no new infra cost.
- **Bad** — additional complexity in outbox table maintenance and relay process.
- **Neutral** — schema registry adoption deferred to ADR-NNNN.
### Confirmation
Compliance verified by:
- ArchUnit rule forbidding direct `KafkaTemplate` use outside outbox-relay module.
- Integration test `OutboxRelayIT` asserting at-least-once delivery with dedup.
- Runtime metric `outbox.lag.seconds` < 5s SLO.
## Pros and Cons of the Options
### Option A — Kafka with transactional outbox
- Good — leverages existing infra.
- Good — fits hexagonal port `DomainEventPublisher`.
- Bad — operational burden of the relay process.
### Option B — RabbitMQ with publisher confirms
- Good — simpler at-least-once model out of the box.
- Bad — introduces a second broker, higher TCO.
### Option C — Direct JDBC + Debezium CDC
- Good — zero application-level publishing code.
- Bad — couples consumers to internal schema; brittle on migrations.
## More Information
- Spike: `docs/spikes/2026-04-kafka-vs-rabbit.md`
- Related: ADR-0003 (use hexagonal architecture), ADR-0005 (transaction port).

Required vs Optional Sections

Section Required Notes
Title yes NNNN. Short title, problem-solution focused.
Context and Problem Statement yes 2–3 sentences or short narrative.
Considered Options yes Bulleted list. At least 2.
Decision Outcome yes Chosen option + one-sentence justification.
Decision Drivers optional Recommended for MADR-full.
Consequences optional Strongly recommended.
Confirmation optional How compliance is enforced (ArchUnit, IT, SLO).
Pros and Cons of Options optional Required when ≥3 options or stakeholders ask.
More Information optional Links to spikes, prior ADRs, RFCs.

Frontmatter

Key Required Values / Format
status yes proposed | accepted | rejected | deprecated | superseded
date yes YYYY-MM-DD, last update
decision-makers recommended YAML list of names or roles
consulted optional YAML list — two-way communication
informed optional YAML list — one-way communication

5. Status Lifecycle

proposed ──► accepted ──► deprecated
│ │
▼ ▼
rejected superseded ──► points to NNNN of replacement

Rules:

  • proposed is the initial state when scaffolded.
  • Move to accepted once decision-makers agree and the PR is approved.
  • rejected is final; keep the file for the historical record.
  • deprecated means the decision is no longer in force but no replacement exists.
  • superseded requires a new ADR; old ADR’s frontmatter gains superseded-by: NNNN and the new ADR’s frontmatter gains supersedes: NNNN.

Immutability: an accepted ADR is append-only. Never edit its content. Fix typos before acceptance only. After acceptance, write a new ADR that supersedes it.

6. Nygard Template (quick capture)

# NNNN. Short Title
## Status
Accepted
## Context
What is the issue we're seeing that motivates this decision.
## Decision
What we are going to do.
## Consequences
What becomes easier or harder.

Use when the decision is obvious and tradeoffs are minimal. Promote to MADR if a reviewer asks “why not X?”.

7. Y-Statement (one-liner)

In the context of <use case>, facing <concern>, we decided for <option> and against <alternatives> to achieve <quality>, accepting <downside>.

Extended form adds because <rationale> and rejected: <list>.

Drop a Y-Statement into a PR description, code comment, or commit message when an ADR is overkill but the reasoning is non-obvious.

8. ISO/IEC/IEEE 42010 (formal)

Nine information items: identifier, summary, decision, status, rationale, alternatives, related decisions, related concerns, related artifacts. Use only in regulated / certification contexts. Out of scope for typical project work.

9. Repo Layout

docs/
└── adr/
├── README.md # decision log index, auto- or hand-curated
├── 0001-record-architectural-decisions.md
├── 0002-use-hexagonal-architecture.md
├── 0003-transaction-port.md
└── ...

docs/adr/README.md lists all ADRs with status and one-line summary. Regenerate or hand-edit on every accepted/superseded change.

10. Workflow

  1. Detect significance — does the change match any trigger in §2?
  2. Run /add-adr "short title" (or scaffold manually).
  3. Fill MADR sections. Set status: proposed.
  4. Open PR. Reviewers comment on the ADR alongside code.
  5. On approval, flip status to accepted in the same PR or a follow-up.
  6. Update docs/adr/README.md index.
  7. Reference the ADR in code via // see ADR-NNNN only when context is non-obvious.

11. Hexagonal Architecture Tie-Ins

ADRs that this project expects, indicative list:

  • 0001 — Use hexagonal architecture (ports & adapters).
  • 0002 — Transaction port over @Transactional on use cases.
  • 0003 — Outbox pattern for domain event publishing.
  • 0004 — Chain of Responsibility for cross-cutting decorators.
  • 0005 — ArchUnit as the layer boundary enforcement gate.
  • 0006 — JsonMapper over ObjectMapper (Jackson 3.x).

When introducing a new bounded context, port, or adapter technology, write an ADR before merging.

12. Review Checklist

Before accepting an ADR:

  • Title is problem-solution oriented, not solution-only.
  • At least 2 options considered.
  • Decision Drivers are measurable (no “better” without a metric).
  • Consequences include at least one negative item.
  • Confirmation lists how compliance is enforced (ArchUnit / IT / SLO / lint).
  • No conflict with existing accepted ADRs (or this one supersedes them explicitly).
  • Filename matches NNNN-title-with-dashes.md and is consecutive.
  • Frontmatter status is proposed at PR open, accepted only after approval.

13. Anti-Patterns

  • ADR for a code-style nit. Use clean-code.md or a lint rule instead.
  • “We picked X because the team likes it” with no driver. Restate the driver.
  • Editing an accepted ADR. Supersede.
  • Single-option ADR. If only one option exists, capture it as a Nygard or skip.
  • ADRs in a wiki separate from the repo. Decisions co-locate with code.
  • Oversize ADRs (>2 pages). Split or link out to a spike.

14. Tooling (optional)

  • adr-tools — bash CLI for scaffolding ADRs. Not required.
  • /add-adr slash command in this repo (see commands/add-adr.md) — preferred for AI-driven flows.
  • Render docs/adr/README.md as the decision log; many static-site generators support it natively.

15. References