Аппендикс
Аппендикс B

Architectural Decision Records (ADR)

Как не забыть, почему вы сделали то, что сделали. И как объяснить это новичкам, себе через полгода и аудиторам.

Как не забыть, почему вы сделали то, что сделали. И как объяснить это новичкам, себе через полгода и аудиторам.


#B.1. Что такое ADR и зачем он нужен

Архитектурное решение — это не выбор фреймворка. Это ответ на вопрос «как мы будем строить систему, чтобы она решала бизнес-задачу и не развалилась через год». Проблема в том, что контекст, в котором принималось решение, живёт ровно до следующего спринта. А потом приходит новый разработчик, смотрит на Kafka в стеке и думает: «Зачем им вообще Kafka? Тут же три запроса в день».

Architectural Decision Record (ADR) — это документ, который фиксирует важное архитектурное решение и, главное, почему вы его приняли. Не просто «мы взяли PostgreSQL», а «мы взяли PostgreSQL, потому что нам нужны ACID-транзакции для платёжной системы, а MongoDB не даёт строгой консистентности, и вот какие альтернативы мы рассматривали».

ADR решает несколько проблем одновременно:

  • Проблема забытого контекста. Через полгода никто не помнит, почему выбрали X вместо Y. ADR — это память команды.
  • Проблема новичков. Новый человек читает ADR и понимает не только текущую архитектуру, но и логику её появления. Он видит не «как есть», а «как мы к этому пришли».
  • Проблема повторения ошибок. Без ADR команды циклически проходят одни и те же обсуждения. «А давайте перепишем на микросервисы?» — «Так мы же это уже обсуждали в ADR-001».
  • Проблема аудита. Если вы проходите SOC2, PCI-DSS или любой другой комплаенс, ADR — это доказательство того, что вы принимали решения осознанно, а не «ну, так получилось».
  • Проблема architect-as-a-service. Когда архитектор уходит из команды или компании, его знания уходят с ним. ADR — попытка оставить хотя бы часть этого знания в коде.

#Когда писать ADR

Простое правило: если решение будет сложно отменить или оно влияет на другие части системы — пишите ADR. Конкретно:

  • Выбор технологии (база данных, брокер сообщений, язык, фреймворк)
  • Архитектурный паттерн (монолит vs микросервисы, CQRS, event-driven)
  • Структура команды или процесса (CI/CD, branching strategy)
  • Security-решение (модель авторизации, подход к secrets management)
  • Data-решение (схема шардинга, стратегия репликации)

Не пишите ADR на каждое решение. Выбор между axios и fetch для HTTP-запросов — это не архитектурное решение. Выбор между REST и gRPC — архитектурное.

#История появления ADR

Термин ввёл Michael Nygard в 2011 году в своей книге «Documenting Architecture Decisions». Идея простая: архитектурные решения должны быть задокументированы так же, как код. Nygard предложил хранить ADR прямо в репозитории — рядом с кодом, а не в Confluence или Notion, где они неизбежно устаревают и теряются.

С тех пор практика ADR стала стандартом де-факто в индустрии. Её используют в Amazon, Spotify, ThoughtWorks и тысячах других компаний. Самое приятное: для внедрения ADR не нужно ни разрешения архитектурного комитета, ни дорогих инструментов. Нужен только репозиторий и дисциплина.


#B.2. Шаблон ADR

Есть много вариаций, но базовый шаблон, предложенный Nygard и развитый сообществом, выглядит так:

# ADR-NNN: [Заголовок решения]

## Статус
[Proposed | Accepted | Deprecated | Superseded]
- Если Superseded — указать, каким ADR заменён

## Контекст
- Какая проблема решается
- Какие ограничения есть (время, бюджет, компетенции команды, compliance)
- Какой контекст важен для принятия решения

## Рассматриваемые варианты
- Вариант A: описание + плюсы и минусы
- Вариант B: описание + плюсы и минусы
- Вариант C: описание + плюсы и минусы

## Решение
Выбран вариант A.
Обоснование: почему A лучше B и C в данном контексте.

## Последствия
- Что изменится в архитектуре
- Что нужно сделать после принятия решения (migration plan, TODO)
- Какие риски остаются и как их митигировать

## Compliance-проверка (опционально)
- Как проверить, что решение выполняется (архитектурные тесты, linter, code review)

Теперь — что писать в каждом поле, а что не писать.

#Статус

Жизненный цикл ADR: Proposed (предложен) → Accepted (принят) → Deprecated (устарел) или Superseded (заменён новым ADR). Ничего сложного.

Важный момент: ADR может быть rejected. Это нормально. Задокументированное rejected-решение не менее ценно, чем принятое. Вы потратили время на анализ, обсудили — записали результат, чтобы не возвращаться к этому вопросу через месяц.

#Контекст

Самая важная часть. Контекст — это причина, по которой решение вообще возникло. Если вы не можете чётко сформулировать контекст — возможно, архитектурного решения и нет, а есть «так захотелось архитектору».

Хороший контекст отвечает на вопросы:

  • Что именно не работает сейчас?
  • Какой бизнес-драйвер подталкивает к решению?
  • Какие нефункциональные требования (latency, availability, consistency) важны?
  • Какие ограничения накладывает реальность (команда из 3 человек, бюджет на инфру $500/мес, deadline — вчера)?

Плохой контекст: «Нам нужна микросервисная архитектура, потому что это современно».

#Рассматриваемые варианты

Не делайте вид, что альтернатив не было. Даже если вы с самого начала знали, что возьмёте PostgreSQL, запишите, что рассматривали MongoDB и почему не взяли. Это защитит от вопроса «а не перейти ли нам на MongoDB?» через полгода.

Для каждого варианта — честно перечислить плюсы и минусы. Без предвзятости. Если у варианта A есть серьёзный недостаток, а вариант B — очевидный фаворит, не надо скрывать недостаток A. Иначе ADR превращается в формальное обоснование уже принятого решения, а не в документ для принятия решения.

#Решение

Чётко: «Выбран вариант A». Без «в целом мы решили попробовать...». И обоснование: почему это лучший выбор в данном контексте. Обоснование — это не «потому что мы так решили», а «потому что приоритет latency над consistency в этом сценарии, и вариант A даёт лучшую latency в ущерб consistency, что приемлемо, так как...».

#Последствия

Здесь важно различать последствия и TODO. Последствия — это то, что изменится в архитектуре: «Вводится новый сервис payments-service, добавляется Kafka как транспорт, event schema публикуется в Schema Registry». TODO — это конкретные шаги: «Написать миграцию, обновить документацию, настроить мониторинг». Последствия остаются в ADR навсегда. TODO живут в трекере задач.

Mitigation рисков — тоже сюда. Если вы знаете, что решение несёт риски (например, выбрали MongoDB, зная, что с транзакциями там сложно), запишите, как эти риски собираетесь закрывать.

#Compliance-проверка (опционально, но рекомендовано)

Это то, что делает ADR живым документом, а не архивной записью. Например: «Сервисы не должны напрямую вызывать базу данных другого сервиса — проверять через архитектурные тесты» или «Все платёжные операции должны проходить через payment-gateway — проверять через code review».

Со временем эти проверки можно автоматизировать (linting, архитектурные тесты — см. Главу 34 про AI-ассистированный code review).

#Где хранить ADR

Золотое правило: ADR хранятся в репозитории вместе с кодом. Не в Confluence, не в Notion, не в Google Docs. Это не «документация» в классическом смысле — это часть инженерной культуры. Когда код и ADR в одном репозитории, они эволюционируют вместе. Когда ADR в Confluence — они умирают через месяц.

Обычная структура:

docs/adr/
├── README.md              ← описание, что такое ADR и как их создавать
├── index.md               ← таблица всех ADR со статусами
├── ADR-001-monolith-vs-microservices.md
├── ADR-002-postgresql-vs-mongodb.md
└── ADR-003-kafka-as-event-bus.md

Некоторые команды используют инструмент adr-tools, который генерирует шаблон, нумерует ADR и обновляет index автоматически. Об этом — в разделе B.4.


#B.3. Примеры ADR

#ADR-001: Выбор между монолитом и микросервисами для стартапа

# ADR-001: Монолит для стартапа (Monolith First)

## Статус
Accepted

## Контекст
Мы запускаем MVP финтех-продукта — платформа для управления
подписками. Команда: 3 разработчика, 1 продакт. Бюджет на инфру —
$200/мес. До первого релиза — 3 месяца.

Основные требования:
- CRUD для подписок, клиентов, платежей
- Интеграция с платёжным шлюзом (Stripe)
- Webhook для обработки событий от Stripe
- Базовая админка

Ограничения:
- Ни у кого в команде нет опыта работы с распределёнными системами
- Бюджет не позволяет нанимать DevOps
- Time-to-market критичен — конкуренты уже на рынке

## Рассматриваемые варианты

### Вариант A: Микросервисы сразу
Разбить на 3-4 микросервиса: subscriptions, payments, notifications, admin.

Плюсы:
+ «Правильная архитектура» с самого начала
+ Можно масштабировать сервисы независимо
+ Команды могут расти независимо (но это в будущем)

Минусы:
- Требует DevOps-инфраструктуры (CI/CD для N сервисов, service discovery,
  API gateway, контейнеризация)
- Сложность дебага и тестирования в 3x выше
- Задержка релиза на 2-3 месяца только на настройку инфраструктуры
- При 3 разработчиках overhead на коммуникацию между сервисами
  съедает весь выигрыш от «независимости»

### Вариант B: Модульный монолит
Один сервис с чёткими модулями (bounded contexts). Каждый модуль
имеет свои модели, свою бизнес-логику, явные интерфейсы.

Плюсы:
+ Быстрый старт — пишем бизнес-логику, а не инфраструктуру
+ Простое тестирование — тесты в одном процессе
+ Один деплой — один CI/CD пайплайн
+ Позволяет позже вынести модули в микросервисы (strangler fig)

Минусы:
- Требует дисциплины в соблюдении границ модулей
- Масштабирование — только целиком
- С ростом команды границы модулей «размываются»

### Вариант C: Монолит («всё в кучу»)
Одна кодовая база без разделения на модули. Быстрый старт.

Плюсы:
+ Максимально быстрый старт
+ Минимальный overhead

Минусы:
- Через 6 месяцев превращается в Big Ball of Mud
- Рефакторинг на микросервисы практически невозможен
- Любое изменение ломает «всё»

## Решение
Выбран вариант B — модульный монолит.

Обоснование:
1. Time-to-market — главный приоритет. Модульный монолит даёт скорость
   стартапа с архитектурой, которую не нужно переписывать через год.
2. Команда из 3 человек не вывезет DevOps-нагрузку микросервисов.
3. Модульная структура (по bounded contexts — подписки, платежи,
   уведомления) позволит вынести модули в отдельные сервисы, когда
   это станет необходимо.
4. Вариант C (Big Ball of Mud) не рассматривался как серьёзная
   альтернатива — экономия времени сейчас обойдётся в 10x времени
   на рефакторинг через год.

## Последствия
- Принято решение начать с модульного монолита на Django
- Каждый bounded context — отдельное Django-приложение (app)
- Модули могут вызывать друг друга только через явные интерфейсы
  (Python ABC), никакого прямого импорта моделей
- Для коммуникации между модулями использовать внутренние события
  (django-signals), не прямые вызовы
- Когда модуль потребует независимого масштабирования — выносим
  через Strangler Fig Pattern

Анти-паттерн, которого избегаем: Distributed Monolith (микросервисы,
которые на самом деле — монолит, но с сетевыми вызовами).

## Compliance-проверка
- [ArchUnit](https://www.archunit.org/) или аналог проверяет,
  что модули не импортируют модели друг друга напрямую
- В code review обращать внимание на cross-module coupling

#ADR-002: Выбор PostgreSQL vs MongoDB для основного хранилища

# ADR-002: PostgreSQL как основная база данных

## Статус
Accepted

## Контекст
Мы строим B2B SaaS-платформу для управления подписками. Основные
сущности: клиенты (accounts), подписки, планы, платежи, инвойсы.
Средняя нагрузка: ~1000 запросов/сек на чтение, ~200 запросов/сек
на запись.

Ключевые требования:
- **Целостность данных**: платежи и инвойсы должны быть консистентны.
  Нельзя списать деньги и не создать инвойс (или наоборот).
- **Транзакции**: часто нужны операции, меняющие несколько сущностей
  атомарно (например, создание подписки + списание + создание инвойса).
- **Сложные запросы**: аггрегация по периодам, аналитические отчёты,
  поиск по разным полям.
- **JSON-данные**: часть данных приходит в неструктурированном виде
  (webhook payloads от Stripe, метаданные).

Ограничения:
- Команда хорошо знает SQL
- Нет жёстких требований к горизонтальному масштабированию
  (хранение на 100K клиентов на старте)

## Рассматриваемые варианты

### Вариант A: PostgreSQL
Реляционная база с поддержкой JSON/BSON полей, ACID, сложных запросов.

Плюсы:
+ ACID-транзакции «из коробки» — критично для платёжных данных
+ Отличная поддержка JSON(B) — можно комбинировать реляционные
  и документные данные в одной базе
+ Богатый SQL — аггрегации, оконные функции, JOIN
+ Зрелое сообщество, огромное количество инструментов
+ Никакого vendor lock-in — PostgreSQL есть везде

Минусы:
- Репликация — только master-replica (no multi-master из коробки)
- Горизонтальное масштабирование (шардинг) — сложно
- Оперативная память — главный ресурс, на больших данных дорого

### Вариант B: MongoDB
Document-oriented NoSQL с гибкой схемой.

Плюсы:
+ Гибкая схема — не нужно миграций при изменении структуры
+ Горизонтальное масштабирование «из коробки» (sharding)
+ Высокая скорость записи (no joins, no transactions overhead)
+ JSON-нативная модель — меньше impedance mismatch с API

Минусы:
- ACID-транзакции появились только в версии 4.0 (2018) и всё ещё
  имеют ограничения (multi-document transactions медленнее)
- JOINs — нет. Приходится денормализовывать или делать в коде.
- Consistency model — в кластере eventual consistency по умолчанию
- При росте данных — сложности с миграцией схемы в production
- Для аналитики нужны дополнительные инструменты (Atlas, aggregation
  pipeline, но это не SQL)

### Вариант C: PostgreSQL + MongoDB
Две базы: PostgreSQL для платёжных данных, MongoDB для неструктурированных.

Плюсы:
+ Каждая база под свою задачу
+ Оптимальная производительность

Минусы:
- В 2 раза больше администрирования
- Сложнее тестирование (нужны обе базы)
- Данные, которые живут в обеих базах (связанные) — синхронизация
- Оверхед не оправдан на текущем масштабе

## Решение
Выбран вариант A — PostgreSQL.

Обоснование:
1. **Платёжные данные требуют ACID**. Никакая гибкость схемы
   не стоит риска потерять деньги клиента.
2. JSONB-поддержка PostgreSQL покрывает наши потребности
   в неструктурированных данных (webhook payloads, metadata).
3. Текущая нагрузка (~1200 rps) PostgreSQL держит без проблем
   на одном инстансе.
4. MongoDB не даёт выигрыша, который оправдал бы потерю
   транзакционной целостности.
5. Вариант C (две базы) — преждевременная оптимизация.

Будущие соображения:
- Если нагрузка на аналитику вырастет — добавим ClickHouse или
  аналогичную аналитическую БД для отчётов (см. Главу 28).
- PostgreSQL останется source of truth для операционных данных.

## Последствия
- Data model проектируется реляционной (FK, constraints, нормализация)
- JSONB поля только для «гибких» данных (metadata, payloads) —
  не как основное хранилище
- Connection pooling через PgBouncer — в плане
- Миграции — через Alembic (или аналог), zero-downtime migrations
  (см. Главу 7)
- Для аналитики — выделенная read replica

## Compliance-проверка
- Code review: проверка, что JSONB не используется для данных,
  которые должны быть в нормализованных таблицах
- Проверка, что критические транзакции обёрнуты в DB-транзакции,
  а не в application-level «транзакции»

#ADR-003: Внедрение Kafka для event-driven коммуникации

# ADR-003: Apache Kafka как брокер событий

## Статус
Accepted

## Контекст
Платформа выросла: теперь у нас 6 микросервисов
(orders, payments, notifications, inventory, analytics, billing).
Синхронные вызовы между сервисами начали создавать проблемы:

1. Каскадные отказы: если payments медленный — падает orders
2. Невозможно replay события (например, пересчитать аналитику
   за прошлый месяц)
3. Нет гарантии доставки при сбое получателя
4. При пиковых нагрузках (Black Friday) сервисы давят друг друга

Нужен event-driven подход. Требования:
- Гарантированная доставка (at-least-once)
- Возможность replay событий
- Порядок событий в рамках одного заказа (ordering guarantee)
- Пропускная способность: минимум 50K событий/сек
- Сохранение истории событий (retention: 30 дней как минимум)

Ограничения:
- Команда имеет базовый опыт работы с очередями (RabbitMQ)
- Бюджет на инфраструктуру: средний (2-3 dedicated сервера)
- Есть requirement на future event sourcing

## Рассматриваемые варианты

### Вариант A: RabbitMQ
Классический message broker, очереди + pub/sub.

Плюсы:
+ Команда знает RabbitMQ
+ Простая настройка и эксплуатация
+ Низкая latency (microseconds)
+ Хорошая поддержка разных паттернов (work queues, pub/sub, RPC)

Минусы:
- Нет retention — сообщение удаляется после ack
- Нет replay — если сообщение потеряно, не восстановить
- Производительность ограничена (~10-20K msg/s на очередь)
- Не подходит для event sourcing и long-term хранения

### Вариант B: Apache Kafka
Log-based stream platform.

Плюсы:
+ Retention по времени/размеру — храним историю
+ Replay — можно перечитать события с любого offset
+ Высокая пропускная способность (~1M msg/s на кластер)
+ Partitioning — ordering в рамках partition
+ Идеально для event sourcing в будущем
+ Интеграция с Kafka Connect, Kafka Streams, Schema Registry

Минусы:
- Сложная эксплуатация (ZooKeeper, брокеры, настройка)
- Выше latency (ms, не μs)
- Больше потребление ресурсов (хранение на диске)
- Команде нужно учиться

### Вариант C: Google Pub/Sub (Cloud-native)
Если бы сидели на GCP.

Плюсы:
+ Zero operations — managed service
+ Автомасштабирование

Минусы:
- Vendor lock-in
- Нет полного контроля над ordering
- Стоимость на высоких объёмах выше, чем self-hosted Kafka
- Ограниченный инструментарий

### Вариант D: Redis Streams
Redis-модуль для стриминга.

Плюсы:
+ Очень низкая latency
+ Простая инфраструктура (Redis уже есть)

Минусы:
- Нет долгосрочного хранения (в памяти)
- Нет гарантий durability при сбое
- Не подходит как source of truth для event sourcing
- Меньше инструментов в экосистеме

## Решение
Выбран вариант B — Apache Kafka.

Обоснование:
1. **Replay — must have.** RabbitMQ (вариант A) отпадает, потому
   что не даёт replay. Kafka — единственный вариант среди
   рассмотренных с нормальным replay и retention.
2. **Будущий event sourcing.** Архитектурная команда планирует
   event sourcing для ключевых бизнес-процессов (см. Главу 7).
   Kafka — естественная основа для этого.
3. **Нагрузка.** 50K событий/сек RabbitMQ вытянет, но на пределе.
   Kafka — с запасом.
4. **Инструментарий.** Kafka Connect для интеграции с внешними
   системами, Kafka Streams для стриминговой обработки — всё это
   пригодится.

Компромисс:
- Более высокая operational complexity оправдана требованиями
  к надёжности и перспективами.
- Для задач, где нужна низкая latency (микросекунды), Kafka
  не подходит — но у нас таких задач пока нет.

## Последствия
- Вводится CI/CD для Kafka: деплой топиков, Schema Registry,
  коннекторов (через GitOps)
- Стандарт: все события имеют Avro-схему, регистрируются
  в Schema Registry
- Правило: сервисы не публикуют события напрямую в топики
  других сервисов — только через собственные топики
- Dead Letter Queue (DLQ) для каждого сервиса — обязательна
- Мониторинг: Kafka Lag, потребители, ошибки десериализации
- Команда проходит обучение Kafka (2 недели)

## Compliance-проверка
- [ ] Все новые топики проходят code review (название, партиции,
      retention, compaction policy)
- [ ] Schema Registry включён — no schemaless events
- [ ] All consumers имеют consumer group и мониторят lag
- [ ] Все обработчики событий идемпотентны (или используют
      idempotency keys)
- [ ] Нет топиков с неограниченным retention

#B.4. Как внедрить ADR в команде

Теория — это хорошо. Но внедрение ADR в реальной команде — это процесс, который ломается на трёх вещах: бюрократизация, заброшенность и сопротивление «зачем нам лишняя бумага».

Вот как это сделать, чтобы ADR прижились.

#Шаг 0: Признать, что ADR — это не документ, а практика

ADR живут, когда они часть процесса разработки, а не «ещё один артефакт, который нужно заполнять». Если ADR пишутся постфактум, когда решение уже принято и реализовано — это не ADR, это мемуары. Их ценность падает в 10 раз.

Правило простое: ADR пишется до или во время принятия решения, а не после. Вы обсуждаете архитектуру — открываете пул-реквест с ADR, обсуждаете его, мержите, потом реализуете.

#Шаг 1: Начать с малого — один ADR на спринт

Не надо внедрять ADR на все решения сразу. Выберите одно решение в спринте, которое явно тянет на архитектурное, и запишите ADR. Остальные пока можно не трогать. Через 2-3 спринта команда привыкнет к формату и начнёт писать ADR сама, без напоминаний.

#Шаг 2: Шаблон в репозитории — quick start

Создайте в docs/adr/ README с описанием процесса и ссылкой на шаблон. Можно даже скрипт, который создаёт новый ADR:

#!/bin/bash
# adr-create.sh
NUM=$(ls docs/adr/*.md 2>/dev/null | wc -l | tr -d ' ')
NUM=$((NUM + 1))
echo "Creating ADR-$NUM"
cp docs/adr/template.md "docs/adr/ADR-$(printf '%03d' $NUM)-$1.md"

Просто, но работает лучше, чем любой tool. А когда привыкнете — можно перейти на adr-tools.

#Шаг 3: ADR как часть Definition of Done

Добавьте в Definition of Done для архитектурных задач пункт: «Решение задокументировано в ADR». Это превращает ADR из «хорошо бы сделать» в «обязательно сделать».

Не надо добавлять ADR в DoD для каждой задачи — только для тех, которые меняют архитектуру. Но критерий «это архитектурное?» должен быть простым: изменение, которое затрагивает больше одного сервиса/модуля — архитектурное.

#Шаг 4: Code review для ADR

ADR — это код. Как и код, его нужно ревьюить. Пул-реквест с ADR ревьюится так же, как пул-реквест с кодом:

  • Контекст описан достаточно?
  • Все альтернативы рассмотрены?
  • Обоснование убедительное?
  • Последствия осознаны?
  • Compliance-проверка реализуема?

Хорошая практика: ADR ревьюит не автор, а другой член команды. Ещё лучше — кто-то, кто будет с этим решением жить.

#Шаг 5: Review устаревших ADR (раз в квартал)

Раз в квартал команда проходит по ADR со статусом Accepted и проверяет: не устарело ли решение? Не появились ли новые обстоятельства, которые меняют выбор?

Если решение устарело — ADR получает статус Deprecated, и открывается новый ADR, который supersedes старый. Это нормальный жизненный цикл. Ничего страшного, если ADR-002 «мы выбрали AWS» заменяется на ADR-008 «мы переезжаем на GCP» — это эволюция архитектуры.

#Инструменты

adr-tools — классика. Bash-скрипты от ThoughtWorks. Создают ADR, нумеруют, обновляют индекс. Не имеют GUI — это feature, не баг. ADR должны жить в текстовых файлах, а не в базе данных.

Установка:

brew install adr-tools   # macOS

Создание ADR:

adr new "Выбор PostgreSQL как основной базы данных"

Создание ADR, который заменяет предыдущий:

adr new -s 2 "Миграция с MongoDB на PostgreSQL"

Посмотреть историю:

adr list

Log4brains — более современный инструмент. Статический генератор сайта для ADR. Парсит markdown-файлы, строит красивую HTML-страницу с историей решений, графами зависимостей, поиском. Работает локально или публикуется на GitHub Pages.

Пример конфига Log4brains (log4brains.yml):

project:
  name: "Subscription Manager"
  adrFolder: "docs/adr"

Использование:

npx log4brains preview   # локальный просмотр
npx log4brains build     # статический сайт в out/

GitHub-экосистема:

  • ADR в markdown — GitHub их отображает
  • Pull Request с ADR — обсуждение через комментарии
  • GitHub Actions: можно добавить линтер для ADR (проверка структуры, обязательных полей)
  • ADR Explorer — браузер ADR прямо в GitHub Pages (через Log4brains или mkdocs)

Более продвинутые штуки:

  • ADR как code owners: через CODEOWNERS можно назначать ответственных за определённые ADR (например, security-ADR ревьюит security team)
  • ADR + архитектурные тесты (см. главу 34): когда ADR-002 утверждает «используем PostgreSQL», можно добавить тест, который проверяет, что код не подключается к MongoDB
  • ADR в CI: на каждый пул-реквест проверять, что если есть архитектурное изменение — приложен ADR (через GPT или простой анализ diff)

#Как не надо внедрять ADR

Несколько анти-паттернов, которые превращают ADR в мёртвый процесс:

«ADR как бюрократия». Если для каждого исправления бага нужно писать ADR — процесс умрёт через неделю. ADR только для решений, а не для задач.

«Архитектурный комитет утверждает ADR». Если ADR утверждаются на еженедельной встрече архитекторов — скорость принятия решений упадёт до нуля. ADR ревьюится в пул-реквесте так же быстро, как код. Не быстрее? Ну так и код не быстрее.

«Напишем ADR после релиза». Написанный постфактум ADR — это не документ решения, а история для внуков. Ценность — ноль. Решение уже принято, альтернативы не рассматривались всерьёз, контекст — это «так сложилось».

«Все ADR должны быть идеальными». Первый ADR команды будет корявым, неполным, с ошибками. Это нормально. Лучше неидеальный ADR, чем идеальный отсутствующий ADR.

#Сколько ADR должно быть в проекте

Нормально: 1 ADR на 1-2 месяца разработки на команду из 5-10 человек. Если у вас 50 ADR за год — вы детально документируете архитектуру. Если 0 — вы ничего не документируете. Если 200 — вы пишете ADR на смену названия переменной.

Крупные проекты (см. Часть VI) могут иметь сотни ADR. В Spotify, например, тысячи ADR по всей организации. Но начинать нужно с малого.


#Ключевые выводы

  1. ADR — это память команды. Не документация, а инструмент, который фиксирует контекст решения. Без контекста решение — просто мнение.

  2. ADR рядом с кодом, не в Confluence. Markdown-файлы в репозитории живут столько же, сколько код. Документы в Confluence умирают в день, когда их автор уходит в отпуск.

  3. Три обязательных поля: контекст, альтернативы, обоснование. Всё остальное — опционально. Без этих трёх полей документ — не ADR.

  4. ADR — процесс, а не артефакт. ADR пишутся до решения, ревьюятся как код и живут вместе с кодом. Если ADR пишется постфактум — его ценность падает в 10 раз.

  5. Анти-паттерн: ADR как бюрократия. Не надо писать ADR на всё. Только на решения, которые сложно отменить или которые влияют на другие части системы.


Ссылки по теме:

  • Книга: Michael Nygard, «Documenting Architecture Decisions» (2011) — оригинальная статья, с которой всё началось
  • Репозиторий: adr-tools — https://github.com/npryce/adr-tools
  • Инструмент: Log4brains — https://github.com/thomvaill/log4brains
  • Статья: «Why write ADRs» на ThoughtWorks Insights
  • Наша Глава 35 — «AI-assisted архитектурное проектирование»: как ADR помогают AI понимать архитектурный контекст
  • Наша Глава 33 — «Промпт-инжиниринг как архитектурная дисциплина»: ADR как контракт с AI