Вопрос, на который никто не может ответить
Почти в любом проекте старше года найдутся решения, причину которых уже никто не помнит. Почему платежи идут через очередь, а заказы - синхронным HTTP? И зачем сервису отдельная база, если в ней пара таблиц? Автор давно в другой команде. В задаче написано “сделать как обсуждали”, а обсуждали на созвоне, который никто не записал.
У команды остаётся два плохих пути. Не трогать: “наверное, была причина”. Или переделать по-своему - и через месяц наступить на ту самую проблему, из-за которой когда-то и выбрали странный вариант.
С этим сталкиваешься почти каждый раз, когда заходишь в проект со стороны, на аудит или техлидом в уже сложившуюся команду. Лечится дёшево: короткой записью о решении в момент, когда его принимают. Называется такая запись ADR, Architecture Decision Record.
Что такое ADR
Один файл на одно решение. Внутри четыре вещи: контекст, варианты, выбор и последствия. Спецификацию или документацию модуля ADR не заменяет - он отвечает на вопрос “почему так”, и больше ни на какой.
Формат предложил Майкл Найгард в 2011 году, и с тех пор он почти не изменился. Набор таких файлов называют журналом решений (decision log). По сути это история проекта: что решили, в каких условиях и чем за это заплатили.
Сильная сторона ADR - размер. Запись помещается на экран, на неё уходит полчаса, поэтому её легко сделать частью обычной работы. Отдельный проект “задокументировать архитектуру”, как известно, не заканчивается никогда.
Какие решения записывать
Записывать каждое изменение - верный способ превратить журнал в свалку, которую через месяц никто не открывает. Правило у меня простое. Если откат решения займёт больше пары дней или заденет чужой код, данные либо публичный контракт, нужна запись.
Типичные кандидаты в backend-проекте:
- RabbitMQ или Kafka для обмена событиями
- модульный монолит или сервисы с первого дня
- синхронный вызов внешнего API или очередь с повторами
- где хранить сессии и как сбрасывать кэш
- формат публичного API: REST или gRPC, как версионировать
- что шифруем и где лежат ключи
Имя пакета, версия линтера, выбор между двумя похожими библиотеками для внутренней утилиты - всё это видно из кода и меняется за минуты. В журнал такое не попадает.
Шаблон
У Найгарда в шаблоне четыре части: статус, контекст, решение, последствия. Я добавляю ещё две, рассмотренные варианты и условие пересмотра. Почему именно их? Когда через год решение приходится объяснять заново, обычно не хватает как раз этих двух.
Пример ниже условный, по мотивам задачи из кейса про онлайн-вклады:
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”. По такому журналу видно и где проект сейчас, и каким путём он сюда пришёл.
Через полгода кто-нибудь обязательно предложит ту же идею снова. Ссылка на отклонённую запись с разбором сэкономит целую встречу.
Где хранить и как встроить в процесс
По умолчанию я кладу записи в 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 никогда не было
Описывать всю историю задним числом не стоит - это неделя работы и куча догадок. Достаточно трёх шагов:
- Первая запись - само решение вести ADR: где лежат файлы, какой шаблон, кто принимает.
- Три-пять самых дорогих из уже принятых решений - короткими записями. Честно пишите, что известно, а что нет: “причины выбора MongoDB не сохранились, сейчас на ней держится история статусов”. Это лучше придуманного обоснования.
- Дальше - только новые решения, по мере появления.
Когда я работаю техлидом на аутсорс, журнал решений - главное, что останется у команды после моего ухода. А на аудите его отсутствие само по себе находка: выходит, знание об архитектуре держится на одном-двух людях.
Почему практика глохнет
- Запись пишут задним числом, чтобы закрыть пункт в чек-листе. Объяснения в ней не будет, будет оправдание.
- В журнал тащат всё подряд. Важное тонет.
- Вместо контекста проекта - пересказ документации о том, как устроена Kafka или что такое CAP, хотя хватило бы ссылки.
- Принятые записи тихо правят задним числом, и журнал начинает врать.
ADR не гарантирует, что решение правильное. Зато его можно понять и спокойно пересмотреть, когда условия изменятся.
Итог
Журналу решений не нужны ни инструменты, ни отдельный процесс. Нужна привычка записывать “почему” рядом с “что”. Если в вашем проекте архитектура живёт в головах двух человек, этот риск дешевле закрыть заранее, чем потом разбирать последствия. Помогу провести аудит архитектуры и завести журнал, который команда дальше будет вести сама - напишите мне в Telegram.