Every engineering team makes architecture decisions. Few of them write the decisions down.
That gap is where most of the long-term pain in a codebase comes from. Six months after a choice is made, nobody remembers why the team picked one message queue over another, which alternatives were rejected, or what trade-off everyone agreed to live with. The decision still shapes the system every day — it just does so invisibly, and every new engineer has to reverse-engineer the reasoning from the code.
Two lightweight documents solve most of this: the RFC (Request for Comments) and the ADR (Architecture Decision Record). They are not heavyweight process. Written well, each one fits on a screen or two, and together they turn “tribal knowledge” into something a newcomer can read in an afternoon.
I leaned on both heavily while leading a horizontal core team at a large European streaming platform — the team responsible for the architectural contracts between product squads. The conventions below are the ones I refined there and now package as a small set of skills I use on every project. Here is how each document works, and how to write one that’s actually worth reading.
First, decide what you’re writing
Before you open a blank page, answer one question: are you still deciding, or have you already decided?
That single distinction drives everything:
RFCs are for discussing. ADRs are for recording.
An RFC is a living document. It exists while a decision is being debated. It can be long, exploratory, and full of alternatives — its job is to provoke good discussion and reach consensus. An ADR is an immutable record. It exists after the decision is made. It must be short, clear, and decisive — its job is to state what was decided and why, forever.
Here is the flowchart I walk through when I’m not sure which one I need:
- Do you actually have a decision to document? If not, stop — you don’t need a document yet.
- Is there already a standard, recommended solution for this?
- Yes, and it’s documented → point people to the existing ADR. Done.
- Yes, but nobody wrote it down → write an ADR to backfill it.
- No standard exists yet. Do you have a proposed solution? If not, it’s too early — go research first.
- Is this a large or breaking change? Think: breaking an existing API, replacing a core framework, anything that crosses team boundaries or changes a company-wide standard.
- Large or breaking → write an RFC first, then an ADR once the dust settles.
- Small and low-risk (adopting a library inside one team, codifying a pattern that’s already emerging) → skip the RFC and write an ADR directly.
| Situation | What to write |
|---|---|
| Existing solution, never documented | ADR (backfill) |
| New problem, small / low-risk change | ADR |
| New problem, large / breaking change | RFC → then ADR |
| A formal process or standard change | RFC → then ADR |
The reassuring part: once an RFC reaches consensus, writing the ADR is almost mechanical. The hard thinking is already done — you’re just recording the outcome in a canonical format.
Writing an RFC
An RFC proposes a solution to a problem and invites the team to challenge it. You write one when the change is big enough that you need buy-in before committing — and when no established answer exists yet.
Give it a title that asks a question
A good RFC title frames a problem or a choice. It should not announce a decision — that comes later. I use a structured scheme so any title is instantly recognizable and searchable:
RFC(<DOMAIN>-<NUMBER>): [<CONTEXT>] <Title>
- Domain (optional): the owning area — backend, frontend, infra, video, data. Omit it if the change crosses everything.
- Number: a unique five-digit ID so the document has a stable handle.
- Context (optional): a platform or sub-scope in brackets, like
[Web]or[Android]. - Title: a problem, a question, or a list of choices.
Good RFC titles read like open questions:
RFC(INFRA-00123): Should we use ECS or EKS to host our containerized applications?RFC(DATA-00456): Choosing a recommendation engine — Personalize, TurboPerso, or MagicTree?
Lead with the problem, not the solution
The single biggest mistake in RFCs is opening with the proposed fix. Readers don’t care about your solution until they feel the problem. So a strong RFC moves in this order:
- Overview — two or three sentences so a reader can decide in 30 seconds whether this concerns them. Write it last.
- Goals and Non-Goals — what you’re trying to achieve, and just as importantly what you are not tackling. Explicit non-goals are what keep a discussion from sprawling.
- Background and Motivation — why this problem exists now, backed by evidence: metrics, incident reports, benchmarks. “Everyone knows our setup is messy” is dismissible; “we run four different HTTP client libraries, each with its own error handling” is not.
- Proposed Solution — concrete, with how it works and how you’ll measure success.
- Alternatives Considered — a short table of options you explored and why you set each one aside. This is the part people skip, and it’s the most valuable: it stops the discussion from relitigating dead ends you already walked down.
- Difficulties, Risks, Rollback — the disruptions you expect, the things that might go wrong, and how you’d back out if it fails. Naming a problem you haven’t solved is better than hiding it.
One framing trick that consistently works: use fear when you want to stop a harmful behavior (what breaks if we don’t change?) and hope when you want to introduce a new one (what gets better if we do?). Match the emotion to the ask.
Run it through its lifecycle
An RFC is alive, and its status says where it is:
DRAFT → DISCUSSING → FINISHED (→ ADR written)
→ ABANDONED
You draft it, set it to DISCUSSING with a target end date, and broadcast it — to your team, to anyone affected, and to a company-wide RFC channel for broad visibility. Then you actually engage: incorporate comments, update the document, answer objections. When consensus lands, you mark it FINISHED and write the ADR that records the decision. If it goes nowhere, you mark it ABANDONED with a sentence explaining why — a dead end documented is still a gift to the next person who considers the same idea.
Writing an ADR
If the RFC is the debate, the ADR is the verdict. It documents what was decided, why, and what follows — and nothing more. It is short by design: if your ADR runs longer than a screen, you’re probably writing an RFC by accident.
The decision formula
Every ADR turns on one sentence, written to a fixed formula:
In the context of [situation or constraint], to answer our need [goal], we use [solution].
For example:
In the context of our containerized microservice architecture and the need to reduce the operational overhead of managing clusters ourselves, we use a managed Kubernetes service to host our containerized applications.
That formula forces the three things that matter — the context, the need, and the decision — into a single, quotable line. Everything else in the document just supports it.
Title it as a statement
Where an RFC title asks a question, an ADR title answers it. Reading the title alone should tell you what to do:
ADR(<DOMAIN>-<NUMBER>): [<CONTEXT>] <Decision>
ADR(INFRA-00123): We use EKS to host our containerized applicationsADR(BACK-00013): Symfony is the framework for new PHP servicesADR(FRONT-00789): [Web] React is our frontend framework
Be honest about consequences
The section that earns an ADR its trust is Consequences, and it must cut both ways. List the positives, yes — but also the trade-offs and the new obligations the decision creates (“all new services must migrate by next quarter”). An ADR that only lists benefits reads like marketing, and people stop believing it. Every real decision costs something; say what.
ADRs are immutable
This is the rule people break most often. Once an ADR is accepted, you do not edit it. If the decision changes, you write a new ADR that supersedes the old one, and you mark the old one SUPERSEDED with a link forward. The record of what you believed, and when, stays intact.
DRAFT → PROPOSED → ACCEPTED
→ REJECTED
ACCEPTED → SUPERSEDED (when a newer ADR replaces it)
A few anti-patterns to avoid: don’t list the options you weighed (“we considered A, B, and C…”) — that’s RFC content. Don’t hedge (“we might use X depending on…”) — an ADR is a decision, not a maybe. And if it’s growing subsections, step back and ask whether the thing you’re really writing is an RFC.
Review before you share
A draft is not ready just because it’s finished. Before an RFC goes out for discussion or an ADR goes up for approval, run it through a deliberate review. I assess every document on three dimensions:
- Clarity — can someone outside the domain follow it? If a document only makes sense to the three people already in the room, it has failed as a record.
- Completeness — are all the load-bearing sections present and meaningful? An RFC with no alternatives is a sales pitch. An ADR with no consequences is a wish.
- Conformance — does it follow the conventions: title format, status set, the decision formula, the right labels?
I sort what I find into three buckets so the author knows what’s urgent:
- Critical — must be fixed before sharing. For an RFC: no clear problem statement, no proposed solution, a missing properties table. For an ADR: a decision that reads like a debate, or a title that doesn’t state the decision.
- Important — should be fixed. No alternatives considered, claims with no evidence, or a consequences section that only lists upsides.
- Suggestion — would strengthen it. A tighter overview, defined success metrics, named stakeholders to keep informed.
The most useful review move is simply quoting the problematic line back to the author and explaining why it trips a reader up. “Our architecture is messy” isn’t a problem statement — “we have four HTTP clients with inconsistent error handling” is. Specific beats polite.
Why this matters
None of this is bureaucracy for its own sake. The whole point is velocity that lasts. When the foundation is documented — when a new engineer can read why each major decision was made — teams move fast and stay fast. When it isn’t, they slow to a crawl, and nobody can quite explain why.
An RFC costs you an afternoon of writing and a week of discussion. An ADR costs you fifteen minutes. The decisions they capture will shape your system for years. That’s an exceptional trade.
This is the same standard I hold my own products to — every architectural choice in Transcriber, Migrate, and e.Vernetzt is one I can explain, because I wrote it down when I made it. If your team is wrestling with keeping architectural integrity across a growing codebase, I’m always open to a conversation. You can reach me at marcelo@binarieslab.com — coffee is on me.