{"index":120,"slug":"editorial-2024-09-practice-adr-decisions","title":"ADR без бюрократии: как сохранить причину технического решения","excerpt":"Практический разбор ADR: от наблюдаемого симптома и цены ошибки до проверяемого решения, отрицательного пути и даты пересмотра.","contentHtml":"
После релиза в коде остаётся необычный обходной путь: запрос проходит через отдельный слой, хотя прямой вызов короче. Через полгода обсуждение исчезает из чата, авторы переключаются на другие задачи, а reviewer видит только итоговый diff. Симптом прост: команда снова спорит, зачем существует условие, очередь или дополнительная граница.
Цена ошибки выше стоимости потерянного контекста. Можно удалить защиту, которая всё ещё нужна для старого ограничения. Можно оставить дорогую схему после того, как ограничение исчезло. Оба решения выглядят разумно, если известен только код. Нужен артефакт, который связывает наблюдаемую проблему с выбором и его последствиями.
Architecture Decision Record фиксирует один значимый выбор. Он отвечает на пять вопросов: что произошло, какие ограничения действовали, какие варианты сравнили, что выбрали и какую цену приняли. Отдельно записывают владельца, статус и сигнал для пересмотра. ADR не объявляет решение вечным и не доказывает, что реализация корректна.
Это важная граница. Ticket хранит работу и сроки. Code review хранит обсуждение изменения. Test проверяет поведение. Runbook описывает операционное действие. ADR хранит rationale — причину, по которой команда выбрала один путь среди допустимых. Ссылка на ticket не заменяет rationale, а commit не заменяет сравнение альтернатив.
Сначала отделите факт от интерпретации. Факт можно увидеть в логе, контракте, trace, конфигурации или наблюдаемом поведении. Интерпретация объясняет факт, но требует проверки. В ADR полезно явно назвать стоимость ошибки: потеря обратимости, рост задержки, новый владелец данных, риск несовместимости или усложнение отката.
Затем ограничьте вопрос. «Как устроить export» слишком широко. «Где caller получает status, если synchronous boundary не выполняется» уже задаёт предмет. Один ADR должен описывать один выбор. Если обсуждение меняет два независимых контракта, разделите записи и свяжите их ссылками.
Варианты сравнивают по одним и тем же критериям. В простом случае достаточно reversibility, соответствия constraint, стоимости эксплуатации и доступного evidence. Не превращайте невыбранные варианты в карикатуры. Если вариант «ничего не менять» не рассматривался, его стоит назвать отдельно: иногда это самый дешёвый и самый обратимый путь.
Title: bounded cache at the BFF boundary\nStatus: Proposed\nOwner: application owner\nReview by: 2025-03-31\n\nContext: source contract permits bounded freshness.\nOptions: direct read; local cache; shared cache.\nDecision: choose local cache with named expiry.\nConsequences: add freshness check and direct-read rollback.\nEvidence question: does the source contract still permit this cache?Пример учебный. Он не создаёт cache, не обращается к repository и не сообщает latency, hit ratio или экономию. Его задача — показать форму записи. В настоящем ADR каждое утверждение о контракте должно ссылаться на доступный артефакт, а владелец должен иметь право проверить его.
| Симптом | Причина | Проверка | Действие |
|---|---|---|---|
| В review спорят о старом обходном пути | Контекст остался в переписке | Найдите исходное ограничение и отделите факт от гипотезы | Создайте proposed ADR и укажите ссылку на code |
| В записи перечислен только выбранный путь | Альтернативы не стали частью решения | Проверьте, можно ли сравнить варианты по одним критериям | Добавьте реально рассмотренные варианты и причины отказа |
| Текст обещает «простую поддержку» | Последствия описаны абстрактно | Спросите, кто что должен сделать и какой риск остаётся | Запишите владельца, cost, rollback и signal пересмотра |
| ADR принят, но code изменился иначе | Запись смешана с реализацией | Сопоставьте decision с контрактом и diff отдельной проверкой | Исправьте code или обновите ADR новым решением; не переписывайте историю |
| Ограничение больше не действует | У записи нет review boundary | Проверьте дату, source contract и сигнал изменения | Создайте successor ADR со статусом superseded для старого |
Хорошая запись начинается с контекста, но не превращается в историю всей команды. Достаточно назвать границу системы, затронутый контракт, decision drivers и цену неверного выбора. Слова «быстрее», «надёжнее» и «проще» требуют уточнения. Быстрее для какого сценария? Надёжнее при каком отказе? Проще для какого владельца?
Последствия должны включать отрицательную сторону. Если выбран local cache, положительный эффект может быть ограничен целевым read path. Цена — необходимость хранить expiry, проверять freshness и иметь путь к direct read. Если данные могут быть чувствительными, добавляется отдельная проверка класса данных. Не прячьте эту цену под словом «trade-off»: читателю нужно понимать, что именно он будет поддерживать.
Запишите две границы: что решение делает и чего оно не делает. В учебном примере мы создаём bounded local state с именованным expiry. Мы не создаём shared invalidation system и не объявляем число запросов измеренным результатом. Такая отрицательная часть защищает от незаметного расширения scope.
ADR может быть логичным и всё равно ошибочным. Он фиксирует состояние знаний на момент выбора. Контракт источника может измениться. Ограничение по данным может оказаться неверным. Операционная цена может вырасти. Поэтому Accepted означает «решение принято», а не «реализация доказана во всех средах».
Свяжите ADR с отдельным evidence question. Для cache это вопрос о допустимой freshness и о том, как обнаружить нарушение. Для миграции это вопрос о совместимости схемы и обратном пути. Для security-решения это вопрос о threat model и обязательной проверке. Не подменяйте evidence красивой формулировкой в разделе Consequences.
ADR не заменяет design document, threat model, migration plan, benchmark, test plan, runbook или incident review. Он не гарантирует полноту альтернатив и не превращает согласие участников в технический факт. Формат нужно подстроить под локальные правила хранения, доступа и approval.
Не редактируйте старую запись так, чтобы она описывала новое решение. История нужна именно для ответа на вопрос «почему раньше сделали так». Если предпосылка исчезла, старый ADR получает статус superseded, а новый record объясняет следующий компромисс. Это сохраняет причинность и не заставляет будущего читателя угадывать, какая версия текста была действующей.
Не называйте synthetic пример production-результатом. Не добавляйте вымышленные числа, названия сервисов и ссылки на несуществующие dashboards. Если факт нельзя проверить, напишите, какой артефакт должен его подтвердить. Такой пробел полезнее уверенного, но ложного вывода.
ADR готов, когда новый читатель без поиска по чату может назвать симптом, constraint, цену ошибки, выбранный вариант и отклонённые альтернативы. Он видит владельца, статус и дату или сигнал пересмотра. Он понимает отрицательный путь и знает, где проверяется реализация. При сравнении с code не возникает скрытого второго решения. Если хотя бы один пункт требует догадки, запись ещё не готова.