← Back to the section

Every project accumulates dozens of choices: you picked ClickHouse instead of PostgreSQL, events instead of REST, a batch consumer instead of a Kafka engine. While a decision is fresh, it lives in the team's collective memory. A year later a new developer arrives — or you yourself after a vacation — and asks: "why was it done this way here?" If the answer sits in a closed chat thread or nowhere at all, the decision will be reopened from scratch, with the same arguments and the same pitfalls.

An ADR (Architecture Decision Record) is a short document that captures one architecture decision: what you decided, in what context, and what it cost. It is not project documentation and not a description of the system — it is one specific choice in one specific situation.

Where the format came from

In 2011 Michael Nygard described a simple template: context, decision, consequences. Later MADR (Markdown Architecture Decision Records) appeared — a slightly extended variant with explicit alternatives. Both come down to the same core; the difference is in the formatting details.

What an ADR looks like

The minimal working format has four sections:

# ADR-007: Events into ClickHouse via a Kafka consumer, not the Kafka engine

Status: accepted (2026-06-06)
Supersedes: —

## Context

Order analytics is moving into ClickHouse (ADR-005). Events already flow
through the outbox into Kafka. We need to choose how to deliver Kafka → ClickHouse:
a consumer inside the service, the ClickHouse Kafka engine, or a Debezium pipeline.

## Decision

A batch consumer inside the orders service.

## Alternatives

- ClickHouse Kafka engine: no application code, but retries,
  mapping and alerts move into DDL that the service team does not see
  and does not review.
- Debezium CDC: mirrors rows, but we need domain events.

## Consequences

+ Mapping and retries live in the service codebase, under normal review and tests.
+ We control idempotency ourselves (ReplacingMergeTree by event_id).
− The consumer is one more service component: deployment, lag monitoring.
− When the event schema changes we edit both the consumer and the table.

Two sections are essential. Context — without it the decision is unreadable: "we took a consumer" is meaningless, while "we took a consumer because ownership of the pipeline must stay with the service team" is knowledge. Consequences with downsides — an ADR without downsides is an advertisement; honest "what it costs us" spares the future reader the illusion that the decision was free.

Statuses and lifecycle

An ADR moves through several statuses: proposedacceptedsuperseded or deprecated.

An important rule: you don't edit an ADR after the fact. If the decision changed, you write a new document, and the old one gets the superseded status with a link to the new one. The history of arguments stays readable rather than being overwritten.

Numbering is continuous and permanent: ADR-007 is never reused, even if the decision is cancelled. This lets you reference a specific document and always find it by its number.

The proposed status is handy during discussion: the ADR is created while still in the pull request, before the decision is accepted. This is the best way to discuss a fork before thousands of lines of code are written.

Where to store them

In the repository, next to the code the decision describes: docs/adr/ADR-007-clickhouse-pipeline.md. Platform-level decisions — service boundaries, shared contracts — are kept in the architecture repository next to the service map.

In a wiki, ADRs die: they aren't reviewed together with the code and aren't found when reading the repository.

When to write an ADR

Write one when a decision is expensive to reverse and non-obvious without context: choosing a storage, an integration method, an event format, a migration strategy, or opting out of something expected ("we don't use this ORM", "we don't version this API").

A good check: if a review or a chat produced an argument longer than ten messages, the outcome of that argument is worth an ADR.

Don't write one for a decision dictated by a team standard (that's what the style guide is for — it is itself one large ADR), for reversible trivia (a package name, a helper library), or for what is obvious from the code. An ADR for every little thing devalues the genre — people stop reading them.

Common mistakes

An ADR written from memory a year later. You decided to "document everything that exists" and wrote twenty ADRs after the fact. The context is already lost, so you got descriptions, not decisions. An ADR is written at the moment of choosing.

An ADR without alternatives. "We decided to take X" — but what did you reject and why? Without the rejected options, the next person will bring them back again.

Editing instead of superseding. You quietly rewrote ADR-003 to fit the new decision — the history died, and links to the old one now lie.

An ADR as a long essay. Ten pages with diagrams is a project document, not an ADR. A decision should read in a couple of minutes; the details go behind links.

The decision exists, the ADR comes "later". "Later" never arrives. The ADR belongs in the definition of done for the task where the fork was decided.

In short

  • An ADR is a short document about one architecture decision: what you decided, why, what the alternatives were, and what it cost.
  • Format: context + decision + alternatives + consequences (with upsides and downsides).
  • Statuses: proposedacceptedsuperseded / deprecated; the old ADR is not edited, a new one is written.
  • Store next to the code in docs/adr/, not in a wiki — otherwise it won't be found or reviewed.
  • Write it at the moment of choosing, not after the fact; put it in the task's definition of done.
  • Don't write one for standard decisions, reversible trivia, or what's obvious from the code.
  • Monolith or microservices — a classic fork whose outcome is worth an ADR.
  • What an architect does — whose job it is to turn a fork into a recorded decision.
  • The C4 model — diagrams describe structure, ADRs describe reasons; they work as a pair.