ADR · Design guide

Architecture decision record

A short record that explains why the system is this way to the people who change it next.

A concise decision record template with clear stewardship, decision authority, options, consequences, and an optional review trigger.

Download adr-template.md ↓View the Markdown example ↓

At a glance

Also called
Architecture decision record
Written by
ADR steward; decision authority accepts or rejects
Ideal length
Nygard’s guideline: one or two pages
Shelf life
Keep accepted and rejected records; supersede only through an accepted successor

An architecture decision record (ADR) records a single architecturally significant decision: its context, status, decision, and consequences. A collection of ADRs forms the decision log; one ADR is a record within that log.

The ADR steward starts and maintains the proposed record, but does not gain decision authority by writing it. The named decision authority accepts or rejects it after review. State the decision plainly and record what becomes easier, harder, or otherwise changes. Keep accepted and rejected decision text and reasoning immutable, apart from status and relationship metadata.

A Proposed ADR can change during review. Accepted is the current decision; Rejected records a decision not taken. Deprecated is an accepted historical decision that must not be chosen for new work, but is not a synonym for Superseded. An ADR is Superseded only when an accepted successor replaces it. Do not name an unaccepted ADR in either supersession field: an accepted successor may set `Supersedes`, then the earlier ADR becomes Superseded and sets `Superseded by`.

Record why the decision made sense under the conditions at the time.

Markdown · adr-007-postgres-queue.md

Compact Markdown example

# ADR-007: PostgreSQL job queue

Illustrative example · all scenario-specific names, dates, figures, and thresholds are fictional · status: Accepted · date: 2026-03-14 · ADR steward: platform lead · decision authority: platform team · supersedes: none · superseded by: none

## Context

The service needs delayed jobs and retries at roughly 50 jobs per minute. The team already operates PostgreSQL but has no production experience with Redis or RabbitMQ. Adding a dedicated queue now would add on-call work before the workload proves that it needs one. A job record must commit in the same PostgreSQL transaction as the application change that requires it.

## Options considered

We considered a PostgreSQL-backed queue, managed Redis, RabbitMQ, and synchronous processing. At the current volume, PostgreSQL is preferred because it commits the job record with the application data and the team already operates it. A dedicated queue could provide more isolation and throughput, but would add a production service before those benefits are demonstrated.

## Decision

We will use a PostgreSQL-backed job queue with a small worker library. It is a deliberate fit for the current volume and operating capability, not a claim that PostgreSQL is the best queue for every workload.

The design has three non-negotiable boundaries:

- Application code commits each job with the application data that requires it. A worker claims work in a short transaction, commits before handler work, and treats a lost or expired claim as stale.
- Every external operation has a durable logical-effect key and immutable payload fingerprint. A timeout or late worker is `unknown` until reconciliation; it never creates a fresh effect by guesswork.
- Order-sensitive effects are allowed only when the recipient enforces a monotonic fence or a durable no-effect tombstone. Local leases coordinate workers but cannot prove that a late network call did not happen.

The implementation record defines `SKIP LOCKED` claiming, lease expiry measured with the database clock (`clock_timestamp()`, not transaction-fixed `now()`), retry limits, idempotent effects, recipient ordering, recovery, and monitoring. Follow the [reliable event processing reference](/design/rfc/reliable-event-processing/) for those details.

## Consequences

This decision favours current operational simplicity over potential future throughput and workload isolation.

### Easier now

- We operate one fewer production system.
- Job enqueueing is atomic with the related application write.
- The team can use its existing PostgreSQL access controls, monitoring, backup process, and operational knowledge.

### Harder now

- Queue traffic shares database capacity with application traffic and adds table-maintenance work.
- The team must maintain and support the worker library.
- The queue is at-least-once, so handlers need durable effect records and idempotent downstream contracts.

## Confirmation or review trigger

Before production use, test atomic enqueueing, concurrent claims, crash recovery, duplicate delivery, unknown downstream results, and the late-worker ordering boundary. The implementation must show one intended effect or a reconciled unknown outcome; it must not silently retry ambiguity.

Review triggers:

- Sustained volume reaches 500 jobs per minute; reassess queue latency and database impact.
- Queue table bloat, lock contention, or job latency degrades normal database work.
- The workload needs more throughput or isolation than the current PostgreSQL design can provide without degrading normal database work.

If any trigger persists for seven days, or causes material impact sooner, propose a successor ADR. Keep this accepted decision text and reasoning unchanged. After an accepted successor replaces it, set this predecessor’s `Status: Superseded` and `Superseded by: ADR-XXX`; in the same coordinated update, set the successor’s `Supersedes: ADR-007`.

Detailed guidance

Use it when

  • A choice affects system structure, key quality attributes, dependencies, interfaces, or construction techniques
  • A decision establishes an engineering pattern that other work will follow
  • A difficult-to-reverse trade-off needs durable rationale and consequences
  • An RFC or design discussion reaches an enduring architecture decision

Skip it when

  • A choice is local, temporary, low-risk, or easy to reverse
  • The choice only follows a current standard or policy, with no local trade-off or exception to record
  • The team is still exploring a broad proposal; use an RFC or design document until one specific decision is ready

What it contains

Number and title
A sequential, never-reused ID and a short title that names the decision.
Status, relationships, and roles
Choose one status. Include the date, ADR steward, and decision authority; add supersession links only after the named successor is accepted.
Context
Factual technical, product, project, and social forces that make a decision necessary.
Options considered
Material alternatives, the status quo when useful, and the relevant trade-offs.
Decision
A clear active-voice statement: “We will …”. For a rejected ADR, state what was rejected and why.
Consequences
Positive, negative, and neutral effects, known risks, and follow-up work.
Confirmation or review trigger (optional)
A compliance check or evidence that should trigger a successor ADR.

Write it well

  • Record one architecturally significant decision per ADR
  • Use a short title and state the decision in active voice
  • Include the factual context and rationale, material alternatives, and all known consequences
  • Name the ADR steward who maintains the record and the decision authority who accepts or rejects it
  • Keep the decision log in one durable place the project team can reach
  • Preserve accepted and rejected decision text, and link successor ADRs in both directions only after acceptance

Avoid

  • Rewrite an accepted or rejected ADR to hide how the decision changed
  • Treat the ADR steward as the decision authority merely because they wrote the record
  • Record every local implementation detail or temporary experiment
  • Omit material alternatives, rationale, negative consequences, or decision ownership
  • Use an ADR as a broad design guide instead of a focused decision record
  • Mark an ADR as superseded, or enter a supersession link, before its replacement is accepted