Skip to content
Knowledge Hub

Beyond ADRs: design docs, Diátaxis, runbooks and C4 diagrams Intermediate

Estimated reading time: 6 min read

This page answers: beyond ADRs, which documents a team needs, who each one is written for, and what it looks like when done right.

A design doc is a relatively informal document written before coding, to lay out the strategy and trade-offs and ask for review. Many companies call it an RFC.

Malte Ubl describes the structure commonly used at Google:

  • Context and scope
  • Goals and non-goals
  • The actual design
  • Alternatives considered
  • Cross-cutting concerns (security, privacy, observability)
Design doc / RFC ADR
Timing Before the work, while things are fuzzy When the decision is settled (often at the end of design review)
Purpose Explore options, invite critique, build consensus Record one decision and its rationale
Scope A whole change, may produce many decisions Exactly one decision
Length Ubl: around 10–20 pages for large projects, a “mini design doc” of 1–3 pages 1–2 pages (Nygard)
Afterwards Gradually goes stale; Ubl advises updating it when the design changes mid-implementation Immutable; supersede to change your mind
Main reader Current reviewers Future engineers

Loading diagram…

A design doc may go stale; an ADR never needs updating.

Ubl also says when not to write a design doc: the problem isn’t ambiguous, or the doc is really an “implementation manual” with no trade-offs. In that case write the ADR directly, or just a good PR description.

Diátaxis: organizing the rest of the docs

Section titled “Diátaxis: organizing the rest of the docs”

The rest of the docs often break down because everything is mixed: a README that is a tutorial, a reference and an architecture explanation all at once. Diátaxis splits documentation by reader need into four types:

Type Diátaxis description Example for the order service Sign it’s written wrong
Tutorial A lesson; the reader learns by doing README quickstart: clone, docker compose up, create the first order, see the event in Kafka Branches like “if you use X then…”; long explanations
How-to guide Directions to reach a real goal, for someone who already knows the job “Replay outbox events for a time range”, “Rotate the Kafka secret”, runbooks Re-teaching concepts from scratch
Reference Accurate, complete, neutral technical description OpenAPI spec, event JSON schemas, env var table with defaults Opinions, “you should”; hand-written and drifting from code
Explanation Context, reasons, the big picture ADRs, architecture.md with C4 diagrams, “Why an order has 7 states” Step-by-step commands mixed in

Two practical tips:

  • Generate reference from code (OpenAPI from NestJS decorators, the env var table from the config validation schema). Anything hand-written will drift.
  • The README is an entry point, not a warehouse. Keep the quickstart short and link to how-tos, reference and docs/adr/.

A runbook is a how-to guide for the person paged at 3 a.m., who may not have written the code. Every alert should link to exactly one runbook; an alert without a runbook is an unfinished alert.

Prometheus describes annotations as the place for longer information such as an alert description or a runbook link:

- alert: OutboxBacklogHigh
# metric exported by the relay: age (seconds) of the oldest pending row
expr: outbox_oldest_pending_age_seconds > 60
for: 5m
labels:
severity: page
annotations:
summary: Outbox has a pending row older than 60s
runbook_url: https://git.example.com/order-service/-/blob/main/docs/runbooks/outbox-backlog.md

The matching runbook:

# Runbook: OutboxBacklogHigh
Alert: oldest pending outbox row > 60s for 5 minutes. Dashboard: <link>
Owner: Checkout team. Last updated: 2026-09-12 (by Minh)
## Impact
Shipping receives OrderPaid late. Payments are NOT affected (see ADR-0012).
## Diagnosis (in order)
1. Is the relay running? `kubectl get pods -l app=outbox-relay`
2. Is Kafka accepting? Check publish errors on the relay dashboard.
3. Is there a "poison" row retrying forever?
SELECT id, event_type, created_at FROM outbox
WHERE published_at IS NULL ORDER BY id LIMIT 20;
## Mitigation
- Relay crash loop: roll the relay back to the previous image.
- Kafka down: do nothing to the outbox; the backlog drains when Kafka returns. Notify #inc-kafka.
- Poison row: do NOT delete. Move it to the outbox_parked table following <link>, open a ticket.
## Escalation
No improvement after 30 minutes: call Checkout's secondary on-call, then SRE.

What makes a runbook usable:

  • User impact stated first.
  • Copy-pasteable commands, in diagnostic order.
  • Explicit list of what not to do.
  • A last-updated date and an owner.

Alert thresholds should come from SLOs (see SLI, SLO and error budget).

Simon Brown’s C4 model has four levels: System context, Container, Component, Code. The C4 site says plainly that system context and container diagrams are enough for most teams; only draw a level if it adds value.

A “container” in C4 is a runtime boundary that runs code or stores data, not a Docker container.

Loading diagram…

How to read it:

Level How to view the diagram above Answers
1 — System context Treat the Order system box as a single box Who uses it, which systems it talks to
2 — Container Open the box Which processes and datastores, and how they communicate
3 — Component Inside the Order API Usually not worth drawing by hand: it changes with every refactor

Rules for keeping diagrams correct:

  • Diagrams are code: Mermaid in docs/architecture.md.
  • Every arrow states a protocol or meaning.
  • A PR that adds a datastore or a new integration must update the diagram.

Next: Keeping docs alive and checklists.