Перейти к содержимому

ADR, C4-модель и RFC: документирование архитектурных решений

Зачем знать на Middle 3: Tech lead — не тот, кто «пишет код быстрее всех», а тот, кто принимает архитектурные решения, документирует их и делает понятными для команды и для будущих разработчиков. Без ADR команда раз в полгода открывает обсуждение «а зачем мы выбрали Kafka?» заново. Без C4-диаграмм новый разработчик неделю «гуляет» по коду. Без RFC процесса решения принимает один человек, а отвечают все.

  1. Концепция: зачем документировать архитектуру
  2. Глубже: ADR, C4-модель, RFC процессы, документация в Go-проектах
  3. Gotchas: типовые ошибки и анти-паттерны
  4. Real cases: Google, Авито, Яндекс, OSS-проекты
  5. Вопросы (20+)
  6. Practice: написать свой ADR/RFC
  7. Источники

Year 1: 3 разработчика. Все знают всё. Решения "обсудили в чате".
Year 2: 15 разработчиков. Половина новая. "А почему мы юзаем Kafka?"
Year 3: 50 человек. Никто не помнит. Все боятся менять.
Year 4: Legacy. Боль.

Решение: документировать решения (не «как написана функция»).

ИнструментЧто документируетКогда
ADRКонкретное архитектурное решение и его обоснованиеКаждое значимое решение
C4Структура системы (визуально)Документация системы
RFCПредложение изменения (с обсуждением)Перед крупными изменениями
  • Onboarding: новый разработчик через неделю понимает «почему так», а не «что есть».
  • Контекст для future-self: через год ты сам забудешь, почему выбрал X.
  • Принятие решений: структурированно, не «на ходу».
  • Командное знание: не bus-factor одного человека.
  • Discoverability: «у нас есть RLS? а кеш где?» — найди в docs.
  • Compliance: аудит спрашивает «почему такая архитектура».

Автор концепции: Michael Nygard, статья “Documenting Architecture Decisions” (2011).

Идея:

  • Каждое архитектурное решение — отдельный markdown.
  • Файлы пронумерованы (immutable): 0001-...md, 0002-...md.
  • Лежат в репо: docs/adr/.
  • Старое решение не удаляют — создают новое, которое его supersedes.

Шаблон (Nygard’s):

# 0042. Использовать Kafka для event bus
## Status
Accepted (2026-03-15)
Supersedes: 0017 — RabbitMQ как messaging.
## Context
- Сейчас RabbitMQ.
- Нужно реплеить события для аналитики на 30 дней.
- Throughput растёт с 10k до 100k msg/sec.
- Нужно partitioning для горизонтального scale.
## Decision
Мигрируем event bus на Apache Kafka.
- Strangler Fig pattern: новые события сразу в Kafka.
- Старые consumers через bridge до Q3.
## Consequences
+ Replay events для аналитики (long retention).
+ Высокий throughput.
+ Schema Registry — единый источник правды для схем.
− Сложнее операционно (Zookeeper/KRaft, partitions).
− Нужна команда обучения.
− Cost +30% инфраструктуры.
## Alternatives considered
- Остаться на RabbitMQ + добавить streams (RMQ 3.9) — недостаточно для long retention.
- NATS JetStream — меньше экосистемы вокруг.
- Redpanda — Kafka-compatible, но vendor lock-in.
## References
- ADR-0017 (RabbitMQ)
- Бенчмарки: notion.so/...
СтатусЧто значит
ProposedПредложен, обсуждается
AcceptedПринят, действует
RejectedОтклонён (с обоснованием)
DeprecatedУстарел, ещё не заменён
Superseded by 00XXЗаменён новым ADR

Immutability: принятый ADR не редактируется. Если решение поменялось — создаётся новый ADR со ссылкой на старый.

  • adr-tools (CLI от Nat Pryce):
    Окно терминала
    adr new "Use Kafka for events"
    adr link 42 supersedes 17
  • log4brains — визуализация ADR в web UI.
  • Markdown в репо — самый простой вариант.
  • Notion / Confluence — для нетехнических команд (но search/diff хуже).

Бэкенд-команда Go-сервиса:

docs/adr/
├── 0001-record-architecture-decisions.md
├── 0002-use-postgresql-as-primary-store.md
├── 0003-use-temporal-for-workflows.md
├── 0004-go-modules-layout.md
├── 0005-use-grpc-for-internal-apis.md
├── 0006-use-otel-for-observability.md
├── 0007-deploy-via-kubernetes.md
├── 0008-feature-flags-via-unleash.md
├── 0009-event-bus-kafka.md (supersedes 0009-rabbitmq if была)
├── 0010-adopt-hexagonal-architecture.md
└── 0042-migrate-to-go-1.24.md

✅ Стоит:

  • Выбор технологии (DB, MQ, lang).
  • Изменение архитектурного паттерна.
  • Решения с долгосрочными последствиями.
  • Trade-off, который не очевиден.

❌ Не стоит:

  • “Использовать tabs вместо spaces” → linting config.
  • “Переименовать функцию X в Y” → код-ревью.
  • “Добавить unit test” → даже не вопрос.

4 уровня абстракции:

Level 1: System Context
↓ zoom in
Level 2: Container (deployment unit)
Level 3: Component (внутри container)
Level 4: Code (рисуют редко — генерится из кода)

«Что у нас за система, кто с ней общается».

┌─────────────────────┐
│ Сustomer │
│ (web/mobile user) │
└─────────┬───────────┘
│ uses
┌─────────────────────┐ ┌──────────────────────┐
│ E-shop System │◄────────┤ Payment Gateway │
│ (наш scope) │ │ (external) │
└─────────┬───────────┘ └──────────────────────┘
│ sends emails
┌─────────────────────┐
│ Email Service │
│ (Mailgun) │
└─────────────────────┘

Pers/actors → System (1 box) → External systems.

«Какие deployable units внутри нашей системы».

E-shop System:
┌───────────┐ ┌───────────┐ ┌───────────┐
│ Web │ │ API │ │ Worker │
│ (React) │ │ (Go) │ │ (Go) │
└─────┬─────┘ └─────┬─────┘ └─────┬─────┘
│ │ │
└──────────────┼──────────────┘
┌────────┴────────┐
▼ ▼
┌───────────┐ ┌───────────┐
│ Postgres │ │ Redis │
│ (orders) │ │ (cache) │
└───────────┘ └───────────┘
│ CDC
┌─────┴─────┐
│ Kafka │
└───────────┘

Container = deployable artifact: микросервис, БД, очередь, фронт.

«Что внутри одного container (модуль)».

API (Go service):
┌────────────────────────────────────────┐
│ │
│ HTTP handlers │
│ ┌──────────────────┐ │
│ │ /orders │ │
│ │ /products │ │
│ │ /users │ │
│ └────────┬─────────┘ │
│ │ │
│ Service layer │
│ ┌────────▼─────────┐ │
│ │ OrderService │ │
│ │ ProductService │ │
│ └────────┬─────────┘ │
│ │ │
│ Repository layer │
│ ┌────────▼─────────┐ │
│ │ PG repository │ │
│ │ Redis cache │ │
│ └──────────────────┘ │
└────────────────────────────────────────┘

Классы/структуры. Обычно не рисуют — UML генерится из кода (godoc).

  • Structurizr (Simon Brown) — DSL → диаграммы:

    workspace {
    model {
    user = person "Customer"
    eshop = softwareSystem "E-shop" {
    api = container "API" "Go"
    db = container "Postgres"
    api -> db "reads/writes"
    }
    user -> api "uses"
    }
    }
  • PlantUML с C4 macros:

    @startuml
    !include https://raw.githubusercontent.com/plantuml-stdlib/C4-PlantUML/master/C4_Container.puml
    Person(user, "User")
    System(api, "API", "Go service")
    Rel(user, api, "uses", "HTTPS")
    @enduml
  • mermaid C4 plugin (с 9.3+):

    C4Context
    Person(user, "User")
    System(api, "API", "Backend")
    Rel(user, api, "uses")
  • draw.io / Lucidchart с C4 stencils — для нетехнических.

  • В репо как docs/architecture/*.dsl (Structurizr) или *.puml.
  • В Markdown через mermaid (рендер в GitHub UI).
  • В Wiki (Confluence/Notion) — но синхронизировать с репо.

Концепция: prior to making a change → write a proposal → team reviews → approve/reject.

Шаблон RFC:

# RFC-0023: Введение feature flags
## Summary
Внедрить LaunchDarkly / Unleash для управления feature flags вместо текущих env vars.
## Motivation
- Выкатки features требуют redeploy.
- A/B-тесты не возможны без feature flags.
- Hot-fix через flag быстрее, чем код-ревью + deploy.
## Detailed design
- Бэкенд интегрируется с Unleash SDK.
- Frontend получает flags через API.
- Flags хранятся в Unleash UI, redux audit log.
- Шаблон в Go:
```go
if unleash.IsEnabled("new-checkout-flow") { ... }
  1. Build in-house — много работы, нужен dashboard.
  2. ConfigCat — pricing хуже.
  3. LaunchDarkly — pricing выше, OSS-альтернатив нет.
  • Phase 1: добавить Unleash в одну фичу.
  • Phase 2: мигрировать env-flag’и.
  • Phase 3: удалить ENV_FEATURE_X из кода.
  • Performance overhead per request?
  • Self-hosted vs managed?
  • @alex (backend lead)
  • @maria (devops)

Under review (deadline 2026-05-30)

### 2.14 RFC vs ADR — в чём разница
| | RFC | ADR |
|---|-----|-----|
| Когда | **Перед** решением | **После** решения |
| Цель | Обсудить, собрать feedback | Зафиксировать решение |
| Состояние | "Under review" → "Accepted/Rejected" | "Accepted" — финальное |
| Изменяется? | Да, по ходу обсуждения | Нет, immutable |
**Pipeline:**
  1. Идея → RFC (draft)
  2. RFC review (1–2 недели)
  3. Approved → принимается решение
  4. Создаётся ADR (зафиксированное)
  5. Реализация
В небольших командах часто **RFC и ADR — один документ**, статусы которого меняются: `Proposed` → `Accepted`.
### 2.15 Engineering culture с RFC
**Google design docs** — RFC по сути:
- Каждый проект начинается с design doc.
- ~10–20 страниц.
- Review by senior engineers.
- Архив (десятки тысяч docs).
**Rust RFCs** — публичный процесс:
- GitHub: rust-lang/rfcs.
- Pull Request = RFC.
- Discussion → FCP (Final Comment Period) → merge/close.
**Kafka KIPs** (Kafka Improvement Proposal):
- Конкретно для Apache Kafka.
- Перед любой фичей — KIP.
- Voting механизм.
**Cassandra CEPs**, **Postgres SIGMOD**, **Linux LKML patches** — похожие подходы.
### 2.16 RFC в РФ-компаниях (2026)
- **Авито** — внутренний RFC процесс, обсуждается архитектура микросервисов.
- **Яндекс** — design docs (внутри Wiki), aдоптируют DDD/EventStorming.
- **Wildberries, Озон** — менее формализовано, но growing.
- **Tinkoff/T-Bank** — engineering design docs для крупных фич.
- **Сбер** — формальные процессы, чаще ADR через Confluence.
### 2.17 Документация Go-проектов
**Минимальный набор:**

my-service/ ├── README.md # Что это, как запустить ├── CONTRIBUTING.md # Как контрибьютить ├── CODEOWNERS # Кто owners каких частей ├── docs/ │ ├── adr/ # ADR │ ├── architecture/ # C4 diagrams │ ├── api/ # OpenAPI/protobuf docs │ ├── runbook/ # Operational procedures │ └── onboarding.md # Onboarding guide └── …

**README structure:**
1. Что делает сервис (1 параграф)
2. Quickstart (как запустить)
3. Configuration (env vars)
4. API summary (ссылка на /docs)
5. Diagrams (C4 context)
6. Links: ADR, runbook, on-call
### 2.18 godoc / pkgsite
```go
// Package orders provides domain logic for processing customer orders.
//
// Architecture follows hexagonal pattern: domain → use cases → adapters.
// See docs/adr/0010-hexagonal-architecture.md for rationale.
package orders
// Order represents a customer purchase.
//
// Order is an aggregate root in DDD terms. State transitions
// are validated through methods (no direct field mutation).
type Order struct {
ID OrderID
CustomerID CustomerID
items []Item // unexported — modifications via AddItem/RemoveItem
status Status
}
// AddItem adds an item to the order.
//
// Returns ErrOrderLocked if the order is already paid or shipped.
func (o *Order) AddItem(item Item) error { ... }

Best practices:

  • Package-level комменты обязательны.
  • Public types/funcs — с примерами (Example...).
  • Линки на ADR для нетривиальных решений.

Не путать с README. Runbook = что делать при инциденте.

# Runbook: API service
## Alerts
### `high_5xx_rate` (> 1% за 5 мин)
1. Check Grafana dashboard https://...
2. Check recent deploys (последние 30 мин)
3. Если новый deploy — rollback: `kubectl rollout undo deployment/api`
4. Проверь логи: `kubectl logs -l app=api --tail=200`
### `db_connections_exhausted`
1. Connection pool — посмотри метрику `pg_stat_activity`
2. Slow queries: `pg_stat_statements` ORDER BY total_time DESC
3. Если запрос — убей: `SELECT pg_terminate_backend(pid)`
## Common operations
### Restart all replicas
`kubectl rollout restart deployment/api`
### Increase replicas
`kubectl scale deployment/api --replicas=10`
## Escalation
- L1 (24/7): on-call schedule в OpsGenie
- L2: @alex (backend lead)
- L3: @cto

Для нового разработчика — пошаговое:

# Onboarding for backend devs
## Week 1
- [ ] Read docs/architecture/overview.md
- [ ] Run service locally (docker-compose up)
- [ ] Read ADR 0001-0010 (foundational decisions)
- [ ] Pair with mentor
## Week 2
- [ ] Pick "good first issue"
- [ ] Submit first PR
## Resources
- Architecture overview: docs/architecture/
- ADR: docs/adr/
- API: see openapi.yaml
- Slack: #backend
- On-call: docs/runbook/

Принципы:

  • Markdown в репо (не Wiki).
  • Versioned с кодом (если поменял API → поменяй docs в том же PR).
  • Build-time validation (mkdocs, mdbook, docusaurus).
  • Auto-publish (GitHub Pages, Vercel, Netlify).

Tools:

  • MkDocs Material — Python, красиво.
  • Docusaurus — React, гибко.
  • mdbook — Rust, простой.
  • Hugo — Go, быстрый.

«Решено использовать Kafka. Точка.» — это не ADR, это указ. Без Context и Consequences ADR бесполезен.

Решение принято в чате 3 месяца назад. ADR никто не написал. Через год все забыли «зачем». Правило: ADR в том же sprint, что и решение.

Кто-то «обновил» ADR-0017 после решения. Теперь не ясно, что было оригинальное обоснование. ADR immutable.

Один раз нарисовали, забыли. Через год реальность другая. Решение: либо как код (Structurizr), либо в onboarding процесс.

Рисовать UML классов вручную = бесполезная работа. Лучше godoc + диаграмма генерируется.

Каждое решение → RFC → 2 недели обсуждений → ничего не сделано. Должен быть fast path для маленьких изменений.

10 человек обсуждают цвет logo вместо архитектуры. Tech lead должен закрывать обсуждение и принимать решение.

Рассинхронизация. Поменяли API → не обновили Wiki. Docs as code — обязательно.

Никто не читает. README = quickstart + ссылки. Детали — отдельно.

Никто не знает «кто owners auth-модуля». При уходе разработчика — boom.

После инцидента не обновили runbook → следующий человек повторяет ошибки.

Не каждое решение требует ADR. «Использовать slog вместо log» — да, ADR. «Назвать переменную userID вместо uid» — нет, code review.


  • ADR в каждом repo сервиса.
  • Шаблон через cookiecutter.
  • Review на GitHub PR.
  • Tagged: technology, security, etc.
  • Все микросервисы в монорепо.
  • Каждый сервис имеет docs/adr/.
  • Глобальные ADR — в docs/global-adr/.
  • C4 диаграммы через Structurizr.
  • В Wiki, шаблон с обязательными секциями.
  • Review by архитектурным комитетом для крупных систем.
  • Архив доступен всем (внутри компании).
  • Сабмит как PR в rust-lang/rfcs.
  • Comment period (10 дней).
  • FCP — Final Comment Period (после approval team).
  • Merge → есть RFC, ещё не реализовано.
  • Tracking issue → реализация.
  • Product Requirements Documents.
  • Перед фичей — PRD + ADR.
  • Tracking через GitHub issues.

  1. Что такое ADR? Кто автор концепции?
  2. Какие основные секции ADR (Nygard’s template)?
  3. Что делать, если решение из ADR устарело?
  4. Чем отличается ADR от RFC?
  5. Опиши 4 уровня C4-модели.
  6. Когда стоит рисовать Level 4 (Code) в C4?
  7. Какие tools для C4 диаграмм ты знаешь?
  8. Опиши процесс «как принять решение через RFC».
  9. Чем Rust RFCs отличаются от Google design docs?
  10. Что такое immutability ADR? Зачем?
  11. Где хранить ADR — в Wiki или в репо? Почему?
  12. Что должно быть в README сервиса?
  13. Что такое CODEOWNERS, как использовать?
  14. Что такое runbook? Чем отличается от README?
  15. Какие принципы «docs as code»?
  16. Когда писать ADR, когда — нет?
  17. Что такое godoc, что туда писать?
  18. Опиши Structurizr DSL.
  19. Как организовать onboarding документацию?
  20. Какие проблемы возникают, если документации нет?
  21. Опиши real-world пример: «ваше решение и почему» в формате ADR.
  22. Что такое bus factor и как ADR его снижает?
  23. Чем mermaid C4 отличается от Structurizr?
  24. Как обновлять C4 диаграммы при росте системы?
  25. Расскажи про KIP/CEP/RFC процессы в OSS.

  1. Возьми реальное решение из своего проекта (например, «выбрали Postgres вместо MySQL»).
  2. Напиши ADR по шаблону Nygard.
  3. Сохрани в docs/adr/0001-...md.
  4. Покажи коллеге — спроси, поняли ли мотивацию.
  1. Нарисуй Level 1 (System Context).
  2. Нарисуй Level 2 (Containers).
  3. Опубликуй в README через mermaid.
  4. Через месяц — проверь, актуально ли.
  1. Возьми планируемое изменение (например, «мигрировать на gRPC»).
  2. Напиши RFC: motivation, design, alternatives, migration.
  3. Сделай review с 2–3 коллегами.
  4. По итогу — создай ADR.
  1. Возьми реальный alert (например, high_cpu).
  2. Напиши steps: что проверить, как остановить, кому эскалировать.
  3. Симулируй инцидент → проверь, помогает ли runbook.
  1. Представь, что приходит новый разработчик.
  2. Напиши docs/onboarding.md — что читать в Week 1, Week 2.
  3. Попроси кого-то новенького пройти по этому документу.
  4. Соберите feedback, обновите.

  1. Michael Nygard — Documenting Architecture Decisions: https://cognitect.com/blog/2011/11/15/documenting-architecture-decisions
  2. ADR GitHub awesome list: https://github.com/joelparkerhenderson/architecture-decision-record
  3. Simon Brown — The C4 model for visualising software architecture: https://c4model.com/
  4. Simon Brown — Software Architecture for Developers (книга)
  5. Structurizr DSL: https://docs.structurizr.com/dsl
  6. PlantUML C4: https://github.com/plantuml-stdlib/C4-PlantUML
  7. adr-tools: https://github.com/npryce/adr-tools
  8. log4brains: https://github.com/thomvaill/log4brains
  9. Rust RFCs: https://github.com/rust-lang/rfcs
  10. Kafka KIPs: https://cwiki.apache.org/confluence/display/KAFKA/KIPs
  11. Google — How we write design docs: https://www.industrialempathy.com/posts/design-docs-at-google/
  12. Software Engineering at Google (книга, главы про design docs)
  13. Fundamentals of Software Architecture — Mark Richards, Neal Ford
  14. Building Evolutionary Architectures — Neal Ford
  15. mermaid C4 syntax: https://mermaid.js.org/syntax/c4.html
  16. Spotify Engineering Blog — practices around RFCs
  17. ThoughtWorks Technology Radar (для контекста, какие решения «в моде»)

Резюме. ADR, C4 и RFC — это не «бюрократия», а инструменты выживания в команде >5 человек. ADR фиксирует решение и его обоснование (immutable). C4 даёт визуальную карту системы на 4 уровнях. RFC — процесс обсуждения перед принятием. Tech lead использует все три инструмента: пишет ADR после каждого значимого решения, поддерживает C4 актуальными, инициирует RFC для крупных изменений. Документация как код, в репо, версионируется вместе с кодом — единственный устойчивый подход.