<- блог
/ 6 мин /
  • архитектура
  • техлидство
  • документация

ADR на практике: как фиксировать архитектурные решения

Через год никто не помнит, почему в проекте очередь, а не синхронный вызов. ADR - короткая запись, которая хранит причину решения рядом с самим решением.

Вопрос, на который никто не может ответить

Почти в любом проекте старше года найдутся решения, причину которых уже никто не помнит. Почему платежи идут через очередь, а заказы - синхронным HTTP? И зачем сервису отдельная база, если в ней пара таблиц? Автор давно в другой команде. В задаче написано “сделать как обсуждали”, а обсуждали на созвоне, который никто не записал.

У команды остаётся два плохих пути. Не трогать: “наверное, была причина”. Или переделать по-своему - и через месяц наступить на ту самую проблему, из-за которой когда-то и выбрали странный вариант.

С этим сталкиваешься почти каждый раз, когда заходишь в проект со стороны, на аудит или техлидом в уже сложившуюся команду. Лечится дёшево: короткой записью о решении в момент, когда его принимают. Называется такая запись ADR, Architecture Decision Record.

Что такое ADR

Один файл на одно решение. Внутри четыре вещи: контекст, варианты, выбор и последствия. Спецификацию или документацию модуля ADR не заменяет - он отвечает на вопрос “почему так”, и больше ни на какой.

Формат предложил Майкл Найгард в 2011 году, и с тех пор он почти не изменился. Набор таких файлов называют журналом решений (decision log). По сути это история проекта: что решили, в каких условиях и чем за это заплатили.

Сильная сторона ADR - размер. Запись помещается на экран, на неё уходит полчаса, поэтому её легко сделать частью обычной работы. Отдельный проект “задокументировать архитектуру”, как известно, не заканчивается никогда.

Какие решения записывать

Записывать каждое изменение - верный способ превратить журнал в свалку, которую через месяц никто не открывает. Правило у меня простое. Если откат решения займёт больше пары дней или заденет чужой код, данные либо публичный контракт, нужна запись.

Типичные кандидаты в backend-проекте:

  • RabbitMQ или Kafka для обмена событиями
  • модульный монолит или сервисы с первого дня
  • синхронный вызов внешнего API или очередь с повторами
  • где хранить сессии и как сбрасывать кэш
  • формат публичного API: REST или gRPC, как версионировать
  • что шифруем и где лежат ключи

Имя пакета, версия линтера, выбор между двумя похожими библиотеками для внутренней утилиты - всё это видно из кода и меняется за минуты. В журнал такое не попадает.

Шаблон

У Найгарда в шаблоне четыре части: статус, контекст, решение, последствия. Я добавляю ещё две, рассмотренные варианты и условие пересмотра. Почему именно их? Когда через год решение приходится объяснять заново, обычно не хватает как раз этих двух.

Пример ниже условный, по мотивам задачи из кейса про онлайн-вклады:

docs/adr/0003-bank-adapters-via-queue.mdmd
1# 0003. Интеграции с банками через адаптеры на очереди
2 
3Статус: принято
4Дата: 2026-03-12
5Заменяет: -
6 
7## Контекст
8Подключаем несколько банков: у одних REST, у других SOAP.
9API банков иногда недоступны по несколько минут.
10Сбой одного банка не должен блокировать заявки в остальные.
11Данные клиентов подпадают под требования ЦБ.
12 
13## Варианты
141. Синхронные вызовы из сервиса заявок. Просто, но сбой банка
15   сразу виден пользователю, а таймауты держат воркеры.
162. Отдельный адаптер на каждый банк, обмен через RabbitMQ.
17   Сбои изолированы, есть повторы. Цена - ещё один компонент
18   в эксплуатации и асинхронный статус заявки.
193. Готовая интеграционная шина. Много возможностей, но долгое
20   внедрение и дорогая поддержка для текущего объёма.
21 
22## Решение
23Сервис заявок публикует событие в RabbitMQ. Адаптер банка
24забирает его, вызывает API и пишет результат обратно.
25Повторы с растущей задержкой, после 5 неудач - в DLQ.
26 
27## Последствия
28+ сбой одного банка не влияет на остальные
29+ новый банк - новый адаптер, ядро не меняется
30- статус заявки асинхронный, клиенту нужен поллинг или push
31- нужен мониторинг очередей и алерт на рост DLQ
32 
33## Когда пересмотреть
34Очередь заявок дольше 10 минут держит больше 1000 сообщений
35или банков становится больше 15.

Как заполнять, чтобы запись работала

разделчто писатьчего избегать
контекстпроверяемые ограничения: нагрузка, сроки, регулятор, навыки командытекст, который подводит к нужному ответу
вариантыдве-три альтернативы, которые реально обсуждализаведомо слабые варианты для массовки
решениедействие, которое можно сверить с кодом"используем X" без объяснения, как именно
последствияплюсы и минусы, причём минусы сразу превращаются в задачиодни плюсы
пересмотрпорог в цифрах: объём, задержка, число интеграций"вернёмся к этому позже"

На две последние строки стоит посмотреть внимательнее. Раздел, где одни плюсы, читается как презентация, и верят ему мало. С минусами интереснее: каждый превращается в конкретную работу - метрику, алерт или тест восстановления из бэкапа. А условие пересмотра гасит вечный спор “не пора ли всё переделать”. Порог пройден? Пишем новый ADR, до тех пор живём с текущим решением.

Статусы и почему принятое не редактируют

Хватает четырёх статусов: предложено, принято, отклонено, заменено.

Принятую запись не переписывают, даже если решение оказалось неудачным. Вместо правки появляется новый ADR со ссылкой на старый, а у старого меняется одна строка - статус: “заменено записью 0017”. По такому журналу видно и где проект сейчас, и каким путём он сюда пришёл.

[INFO]Отклонённый ADR тоже полезен

Через полгода кто-нибудь обязательно предложит ту же идею снова. Ссылка на отклонённую запись с разбором сэкономит целую встречу.

Где хранить и как встроить в процесс

По умолчанию я кладу записи в docs/adr того репозитория, к которому относится решение. Формат - Markdown, в имени номер и пара слов: 0003-bank-adapters-via-queue.md. Ревью запись проходит в том же pull request, что и код, и потому не отстаёт от реальности.

Порядок важен. Сначала ADR в статусе “предложено” и обсуждение, код - потом. Если писать запись, когда всё уже сделано, контекст незаметно подстраивается под результат, а неудобные варианты просто забываются.

Что помогает практике прижиться:

  • пункт “нужен ли ADR?” в шаблоне pull request
  • docs/adr/README.md со списком записей и статусами, без него записи никто не найдёт
  • CODEOWNERS на docs/adr, чтобы запись не принимали без техлида
  • ссылка на ADR из задачи и из комментария рядом с неочевидным кодом
  • adr-tools или log4brains - если хочется создавать файлы по шаблону и собирать индекс автоматически

Решение задевает сразу несколько репозиториев? Тогда нужен общий репозиторий архитектуры, лишь бы у записей был владелец и история изменений.

ADR и RFC

RFC - площадка для обсуждения большого изменения: подробный документ с планом миграции, рисками и открытыми вопросами. ADR потом фиксирует итог в паре абзацев и ссылается на RFC. Небольшим решениям отдельный RFC ни к чему, там ADR в статусе “предложено” сам служит предметом обсуждения.

Как начать в проекте, где ADR никогда не было

Описывать всю историю задним числом не стоит - это неделя работы и куча догадок. Достаточно трёх шагов:

  1. Первая запись - само решение вести ADR: где лежат файлы, какой шаблон, кто принимает.
  2. Три-пять самых дорогих из уже принятых решений - короткими записями. Честно пишите, что известно, а что нет: “причины выбора MongoDB не сохранились, сейчас на ней держится история статусов”. Это лучше придуманного обоснования.
  3. Дальше - только новые решения, по мере появления.

Когда я работаю техлидом на аутсорс, журнал решений - главное, что останется у команды после моего ухода. А на аудите его отсутствие само по себе находка: выходит, знание об архитектуре держится на одном-двух людях.

Почему практика глохнет

  • Запись пишут задним числом, чтобы закрыть пункт в чек-листе. Объяснения в ней не будет, будет оправдание.
  • В журнал тащат всё подряд. Важное тонет.
  • Вместо контекста проекта - пересказ документации о том, как устроена Kafka или что такое CAP, хотя хватило бы ссылки.
  • Принятые записи тихо правят задним числом, и журнал начинает врать.
ADR не гарантирует, что решение правильное. Зато его можно понять и спокойно пересмотреть, когда условия изменятся.

Итог

Журналу решений не нужны ни инструменты, ни отдельный процесс. Нужна привычка записывать “почему” рядом с “что”. Если в вашем проекте архитектура живёт в головах двух человек, этот риск дешевле закрыть заранее, чем потом разбирать последствия. Помогу провести аудит архитектуры и завести журнал, который команда дальше будет вести сама - напишите мне в Telegram.


кейсы

Как это выглядит в проектах

Задачи, архитектура и результаты.
проект

Обсудить проект

Разработка под ключ, backend, техлидство, аудит.