{ "index": 119, "slug": "editorial-2024-09-mechanism-adr-decisions", "title": "ADR без иллюзии выбора: как записать решение, его цену и путь назад", "excerpt": "Архитектурное решение теряет смысл, если в записи виден только победивший вариант. Разбираем, как сравнить альтернативы по одной схеме, проверить отрицательный путь и оставить условие для пересмотра.", "contentHtml": "

В review появляется знакомый симптом: команда обсуждает не условие задачи, а вкус. Один вариант называют простым, другой — надёжным, третий — слишком дорогим. В ADR уже записан победитель, но альтернативы сведены к двум словам. Через месяц авторы помнят контекст, а остальные видят только решение. Через год никто не знает, какую проблему оно закрывало и что делать, если исходное условие изменилось.

\n

Цена ошибки растёт вместе с границей решения. Неудачный выбор может закрепить общий протокол, миграцию, формат данных или операционную обязанность. Тогда быстрый первый merge скрывает дорогой rollback. Ошибка не в том, что команда выбрала неидеальный вариант. Ошибка в том, что запись не показывает принятый компромисс и не даёт проверить, когда его пора пересмотреть.

\n

Тезис: хороший ADR не доказывает, что выбранный вариант лучший вообще. Он связывает наблюдаемую проблему с конкретным ограничением, одинаково описывает альтернативы, называет цену выбора и задаёт сигнал для возврата к решению. Числа могут помочь в учебной модели, но не заменяют факты и не превращают мнение в production-метрику.

\n

Механизм: от симптома к решению

\n

ADR полезен как короткая цепочка причин. Сначала автор отделяет факт от объяснения. Факт можно наблюдать: caller должен получить статус, пока фоновая операция продолжается; данные нельзя отдавать старше заданной границы; изменение должно откатываться без записи нового формата. Гипотеза объясняет, почему текущий путь не подходит. Решение отвечает на ограничение, а не на абстрактную цель вроде «улучшить архитектуру».

\n

У каждой альтернативы должна быть одна и та же карточка. Запишите, какой constraint она закрывает, где находится её граница, кто владеет дополнительной работой, как выглядит возврат и какое evidence уже есть. Отсутствующее evidence тоже является результатом сравнения. Оно ограничивает уверенность, но не доказывает безопасность или опасность варианта.

\n

Статус помогает не смешивать обсуждение и историю. Proposed означает, что запись готова к проверке. Accepted фиксирует принятое решение. Superseded означает, что новый ADR заменил старый и объяснил причину. Статус не запускает миграцию, не назначает approval и не проверяет rollback автоматически. Эти действия должны иметь отдельного владельца и отдельный сигнал.

\n
symptom      = caller не получает понятный status\nconstraint   = acknowledgement не должен зависеть от business completion\nalternatives = direct response | bounded async state | shared workflow\ndecision     = bounded async state у границы сервиса\nprice        = expiry, owner состояния, cleanup и отдельная проверка retry\nreassess     = меняется контракт caller или срок хранения состояния
\n

Этот фрагмент — учебный пример формы. Он не описывает реальный сервис, трафик, SLO или production-результат. Его задача — показать, как решение связывает симптом, условие, цену и отрицательный путь. В настоящем ADR вместо учебных утверждений нужны ссылки на контракт, issue, тест, threat model или другой разрешённый источник evidence.

\n

Как сравнить альтернативы честно

\n

Сначала выровняйте уровень вариантов. Нельзя сравнивать «локальный cache» с «переделать платформу»: это разные масштабы и владельцы. Сформулируйте варианты на уровне решения, а затем укажите реализацию как следствие. Для asynchronous boundary это могут быть прямой ответ после завершения работы, bounded state с выдачей статуса и общий workflow с отдельным хранением состояния.

\n

Constraint fit отвечает на вопрос «закрывает ли вариант обязательное условие». Reversibility описывает не наличие кнопки undo, а область возврата, порядок действий и владельца. Evidence fit показывает, какие факты поддерживают выбор и какой вопрос остался открытым. Operating cost называет долг: expiry, retry, cleanup, миграцию, поддержку контракта или ручной review. Reassessment показывает, что должно измениться, чтобы открыть новый ADR.

\n
КритерийВопрос к каждому вариантуПроверяемый артефактЧего нельзя утверждать
Constraint fitКакое объявленное условие выполняется?Контракт, problem statement или acceptance questionЧто вариант оптимален для всех целей
ReversibilityКакой scope возврата, кто его выполняет и что останавливает change?План rollback и граница затронутого состоянияЧто rollback уже проверен в production
Evidence fitКакой факт поддерживает выбор и чего пока не хватает?Ссылка на источник, controlled test или явное unknownЧто отсутствие данных равно безопасному результату
Operating costКто поддерживает status, expiry, retry, cleanup или migration?Owner и consequence в ADRЧто стоимость измерена в часах или деньгах
ReassessmentКакой сигнал отменяет исходное предположение?Review condition, metric question или contract linkЧто дата сама запустит пересмотр
\n

Таблица не требует единой оценки. Она делает пропуски видимыми. Если у одного варианта есть контракт, а у другого только слово «сложно», сравнение ещё не началось. Если команда всё же применяет score, заранее закрепите шкалу, веса и смысл баллов. Рядом напишите, какие данные модель не учитывает. Итоговый балл может быть tie-breaker для обсуждения, но не доказательством корректности.

\n
\"Синтетическая
Иллюстрация показывает синтетическую матрицу. Значения помогают увидеть форму сравнения и не являются метриками реальной команды или production-системы.
\n

Пример записи с отрицательным путём

\n

Предположим, synchronous caller ждёт результат долгой операции. Прямой ответ сохраняет простую модель, но не выдерживает границу времени. Shared workflow даёт общий status, но добавляет cross-service owner и отдельный контракт. Bounded state у границы сервиса отделяет acknowledgement от business completion. Это может закрыть исходное условие, если срок хранения, повтор запроса и очистка состояния определены явно.

\n
## Decision\nВыбираем bounded state у границы сервиса.\n\n## Consequences\nПоложительные: caller получает отдельный status.\nЦена: owner хранит expiry и cleanup; retry должен быть идемпотентным.\n\n## Не делаем\nНе вводим shared workflow и не считаем локальное состояние\nуниверсальным механизмом координации.\n\n## Reassessment\nОткрываем новый ADR, если caller требует общего status между\nсервисами или срок хранения превышает допустимую границу.
\n

Отрицательный путь защищает запись от незаметного расширения. Без него локальное решение постепенно начинает обслуживать новые callers, а временное поле превращается в общий протокол. Если новое требование действительно появилось, это не повод молча дописывать старый ADR. Нужен successor с новым контекстом, альтернативами и ценой. Старый record остаётся историей прежнего компромисса.

\n

Симптом → причина → проверка → действие

\n
СимптомПричинаПроверкаДействие
Выбранный вариант подробно описан, остальные названы «сложными»Автор сравнил не варианты, а привлекательность собственного решенияЗаполнить одну карточку criteria для каждой альтернативыПереписать ADR и назвать explicit downside победителя
После merge спорят, что на самом деле означал ADRВ записи смешаны факт, гипотеза и implementation detailПометить источник каждого важного утвержденияВынести неизвестное в evidence gap и добавить owner проверки
Rollback существует только как фразаНе определены scope, порядок и stop conditionПровести dry run на учебной копии или описать точную границуСвязать ADR с отдельным rollback-планом и не объявлять его проверенным без evidence
Локальный механизм используют новые callersОтрицательный путь и граница применимости не записаныПроверить список потребителей и контракт состоянияОстановить расширение, создать successor ADR при новом требовании
Дата review прошла, но никто не вернулся к решениюДата ошибочно принята за автоматический процессНайти владельца сигнала и фактическое событие пересмотраНазначить действие отдельно или заменить дату проверяемым trigger
\n

Порядок работы

\n
  1. Сузьте вопрос. Опишите одну границу: какой caller, какое состояние и какое обязательное условие создают проблему.
  2. Зафиксируйте наблюдаемый симптом и цену. Отделите факт от гипотезы. Назовите, что станет дороже или опаснее при неверном выборе.
  3. Соберите три разумные альтернативы. Приведите их к одному уровню и не добавляйте вариант, который не может закрыть constraint.
  4. Заполните одинаковые поля. Укажите constraint fit, reversibility, evidence gap, operating cost, owner и отрицательный путь.
  5. Сформулируйте decision как компромисс. Запишите, что реализуем и чего намеренно не делаем.
  6. Назначьте проверяемый сигнал. Это может быть изменение контракта, новый потребитель, исчерпание срока хранения или конкретный вопрос к метрике.
  7. Проверьте запись до implementation. Reviewer должен суметь назвать выбранный вариант, его цену и условие пересмотра, не открывая код.
  8. Свяжите запись с реализацией отдельно. ADR не заменяет тест, threat model, migration plan, benchmark, runbook или incident review.
\n

Ограничения и критерий готовности

\n

ADR не делает решение истинным и не заменяет исследование. Он не измеряет latency, надёжность, стоимость или безопасность, если рядом нет соответствующего evidence. Он не гарантирует, что команда найдёт все альтернативы. Формат может различаться: важнее сохранить контекст, выбор, статус, последствия, границы и путь пересмотра. Для legal, security, privacy и data boundary нужны отдельные проверки с отдельными владельцами.

\n

Не добавляйте в учебный пример вымышленные traffic, error rate, savings или outcome. Если число нужно для объяснения score, пометьте его как синтетическое и не переносите вывод за пределы модели. Если production-данных нет, честная формулировка — «данных пока недостаточно», а не «вариант доказанно безопасен».

\n

Запись готова, когда независимый читатель может ответить на пять вопросов: какой симптом наблюдали; какое constraint обязателен; почему сравнивали именно эти варианты; какую цену принимает выбранный путь; какой сигнал заставит открыть новое решение. Проверка проста: уберите из текста заголовок Decision и попросите коллегу восстановить его из context, alternatives и consequences. Если он не может назвать отрицательный путь или owner, ADR ещё не готов.

\n

Проверяемые источники

\n" }