{ "index": 119, "slug": "editorial-2024-09-mechanism-adr-decisions", "title": "ADR без иллюзии выбора: как записать решение, его цену и путь назад", "excerpt": "Архитектурное решение теряет смысл, если в записи виден только победивший вариант. Разбираем, как сравнить альтернативы по одной схеме, проверить отрицательный путь и оставить условие для пересмотра.", "contentHtml": "
В review появляется знакомый симптом: команда обсуждает не условие задачи, а вкус. Один вариант называют простым, другой — надёжным, третий — слишком дорогим. В ADR уже записан победитель, но альтернативы сведены к двум словам. Через месяц авторы помнят контекст, а остальные видят только решение. Через год никто не знает, какую проблему оно закрывало и что делать, если исходное условие изменилось.
\nЦена ошибки растёт вместе с границей решения. Неудачный выбор может закрепить общий протокол, миграцию, формат данных или операционную обязанность. Тогда быстрый первый merge скрывает дорогой rollback. Ошибка не в том, что команда выбрала неидеальный вариант. Ошибка в том, что запись не показывает принятый компромисс и не даёт проверить, когда его пора пересмотреть.
\nТезис: хороший ADR не доказывает, что выбранный вариант лучший вообще. Он связывает наблюдаемую проблему с конкретным ограничением, одинаково описывает альтернативы, называет цену выбора и задаёт сигнал для возврата к решению. Числа могут помочь в учебной модели, но не заменяют факты и не превращают мнение в production-метрику.
\nADR полезен как короткая цепочка причин. Сначала автор отделяет факт от объяснения. Факт можно наблюдать: caller должен получить статус, пока фоновая операция продолжается; данные нельзя отдавать старше заданной границы; изменение должно откатываться без записи нового формата. Гипотеза объясняет, почему текущий путь не подходит. Решение отвечает на ограничение, а не на абстрактную цель вроде «улучшить архитектуру».
\nУ каждой альтернативы должна быть одна и та же карточка. Запишите, какой constraint она закрывает, где находится её граница, кто владеет дополнительной работой, как выглядит возврат и какое evidence уже есть. Отсутствующее evidence тоже является результатом сравнения. Оно ограничивает уверенность, но не доказывает безопасность или опасность варианта.
\nСтатус помогает не смешивать обсуждение и историю. Proposed означает, что запись готова к проверке. Accepted фиксирует принятое решение. Superseded означает, что новый ADR заменил старый и объяснил причину. Статус не запускает миграцию, не назначает approval и не проверяет rollback автоматически. Эти действия должны иметь отдельного владельца и отдельный сигнал.
\nsymptom = 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Сначала выровняйте уровень вариантов. Нельзя сравнивать «локальный cache» с «переделать платформу»: это разные масштабы и владельцы. Сформулируйте варианты на уровне решения, а затем укажите реализацию как следствие. Для asynchronous boundary это могут быть прямой ответ после завершения работы, bounded state с выдачей статуса и общий workflow с отдельным хранением состояния.
\nConstraint 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 | Что дата сама запустит пересмотр |
Таблица не требует единой оценки. Она делает пропуски видимыми. Если у одного варианта есть контракт, а у другого только слово «сложно», сравнение ещё не началось. Если команда всё же применяет score, заранее закрепите шкалу, веса и смысл баллов. Рядом напишите, какие данные модель не учитывает. Итоговый балл может быть tie-breaker для обсуждения, но не доказательством корректности.
\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| Симптом | Причина | Проверка | Действие |
|---|---|---|---|
| Выбранный вариант подробно описан, остальные названы «сложными» | Автор сравнил не варианты, а привлекательность собственного решения | Заполнить одну карточку 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 |
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