Files
progcode/editorial/agent-rewrites/119.json
T

8 lines
29 KiB
JSON
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
{
"index": 119,
"slug": "editorial-2024-09-mechanism-adr-decisions",
"title": "ADR как журнал компромисса: решение, цена и путь назад",
"excerpt": "Хороший ADR сохраняет не только выбранный вариант, но и исходное ограничение, отклонённые альтернативы, цену решения и сигнал для пересмотра. Разбираем это на сценарии долгого экспорта.",
"contentHtml": "<p>После ревью в репозитории остаётся новый endpoint, очередь или дополнительное хранилище, но исчезает причина выбора. Через несколько месяцев команда видит только реализацию: один caller ждёт ответ, другой опрашивает status, третий уже использует внутреннее поле как публичный контракт. Вопрос «почему здесь так?» снова уходит в поиск по чатам и памяти участников.</p>\n<p>Ошибка не обязательно в самом техническом выборе. Дорого обходится потеря его границ: неизвестно, какое ограничение считалось обязательным, какие варианты сравнивали, кто владеет состоянием и что делать после изменения исходных условий. ADR — Architectural Decision Record, запись архитектурного решения — нужен именно для этой причинной связи. Он не делает решение правильным навсегда и не заменяет тестирование, но оставляет проверяемую историю компромисса.</p>\n<p><strong>Тезис статьи:</strong> ADR готов, когда независимый читатель может восстановить проблему, обязательное условие, выбранный путь, его отрицательные последствия и сигнал пересмотра. Слова «быстрее», «проще» и «надёжнее» становятся полезными только после уточнения: для какого участника, при каком отказе и каким артефактом это проверяется.</p>\n<h2>Что именно фиксирует ADR</h2>\n<p>Официальное сообщество ADR определяет architectural decision как обоснованный выбор, значимый для архитектуры, а ADR — как запись одного такого выбора и его rationale, то есть причины. Коллекция записей образует decision log. Поэтому один ADR отвечает на один связный вопрос, а не пытается стать полной технической документацией системы.</p>\n<p>В шаблоне Michael Nygard минимальная форма состоит из <em>Status</em>, <em>Context</em>, <em>Decision</em> и <em>Consequences</em>. В MADR форма расширяется полями decision drivers, considered options, outcome и confirmation. Это полезные шаблоны, а не универсальный закон: команда может добавить владельца, ссылки на контракт, confidence или условие пересмотра.</p>\n<table><caption>Граница ответственности ADR и соседних артефактов</caption><thead><tr><th scope=\"col\">Артефакт</th><th scope=\"col\">Главный вопрос</th><th scope=\"col\">Что должно остаться внутри</th><th scope=\"col\">Чего он не доказывает</th></tr></thead><tbody><tr><td>ADR</td><td>Почему выбран этот вариант?</td><td>Контекст, ограничения, альтернативы, решение и последствия</td><td>Что код уже корректен во всех средах</td></tr><tr><td>Design document</td><td>Как устроить решение целиком?</td><td>Компоненты, потоки, контракты и детали реализации</td><td>Что выбранный дизайн принят как долгосрочное решение</td></tr><tr><td>Test</td><td>Какое поведение воспроизводится?</td><td>Условия, входы, ожидаемый результат, регрессия</td><td>Почему выбрана именно эта архитектура</td></tr><tr><td>Runbook</td><td>Что делать при операционном событии?</td><td>Команды, роли, stop condition и восстановление</td><td>Что исходный компромисс всё ещё применим</td></tr></tbody></table>\n<p>Ссылка на ticket или commit полезна для навигации, но не заменяет rationale. И наоборот, ADR не должен превращаться в копию implementation guide. Если решение требует детальной схемы, threat model или плана миграции, запишите выбор в ADR и свяжите его с отдельным документом.</p>\n<h2>Сначала зафиксируйте симптом и границу</h2>\n<p>Начинайте с наблюдаемого симптома, а не с названия технологии. Например: «клиент вызывает экспорт, но операция иногда не укладывается в границу HTTP-запроса; при повторе непонятно, создан ли второй экспорт». Это факт и вопрос. Гипотеза «нам нужна очередь» появляется только после проверки длительности операции, повторяемости запроса и требований caller.</p>\n<p>Затем назовите цену ошибки. В этом сценарии неверный выбор может дать дубли экспорта, зависшие записи, неограниченное хранение результатов или расхождение между статусом и фактическим завершением. Цена должна быть привязана к системе: какой ресурс останется, кто его удаляет, какая граница времени действует, какой контракт увидит клиент.</p>\n<p>Полезно записать decision drivers — факторы, по которым варианты будут сравниваться. Для экспорта это могут быть ограничение времени ответа, идемпотентность повторного запроса, наблюдаемость состояния, стоимость хранения и возможность вернуться к синхронному пути для коротких файлов. Если driver нельзя проверить или ему нет владельца, это пока предположение, а не доказательство.</p>\n<pre><code>Проблема: export иногда дольше жизни HTTP-запроса.\nОбязательные условия:\n- клиент получает подтверждение создания операции;\n- повтор запроса не создаёт второй экспорт;\n- состояние имеет срок хранения и владельца очистки.\nЦена ошибки: дубль работы или запись, которую никто не удаляет.\nВопрос решения: где хранить status и кто отвечает за его переходы?</code></pre>\n<p>Фрагмент — учебная заготовка. Он не сообщает реальную длительность, нагрузку или процент ошибок. В рабочей записи эти утверждения должны ссылаться на контракт, trace, тест, incident или другой доступный источник. Если данных нет, оставьте <em>evidence gap</em> и действие по его закрытию.</p>\n<h2>Сравнивайте альтернативы одной меркой</h2>\n<p>Сравнение ломается, когда выбранный вариант описан конкретно, а остальные получают ярлык «сложно». Приведите варианты к одному уровню и задайте одинаковые вопросы: выполняет ли вариант обязательные условия, где хранится состояние, как работает повтор, кто поддерживает очистку, как выглядит возврат и какое evidence уже есть.</p>\n<table><caption>Учебное сравнение способов выполнить долгий экспорт</caption><thead><tr><th scope=\"col\">Вариант</th><th scope=\"col\">Что закрывает</th><th scope=\"col\">Цена и риск</th><th scope=\"col\">Путь назад</th></tr></thead><tbody><tr><td>Синхронный ответ</td><td>Простая модель для короткой операции</td><td>Не выдерживает временную границу; повтор может повторить работу</td><td>Не требуется отдельное состояние, но граница остаётся нерешённой</td></tr><tr><td>Очередь и status у сервиса экспорта</td><td>Отделяет подтверждение создания от завершения работы</td><td>Нужны idempotency key, срок хранения, cleanup и наблюдение переходов</td><td>Остановить новые async-запуски и вернуть короткие операции на прямой путь</td></tr><tr><td>Общий workflow между сервисами</td><td>Единый статус для нескольких владельцев процесса</td><td>Появляется общий контракт, координатор и cross-service ownership</td><td>Удалить интеграцию нельзя одной заменой; сначала нужен план вывода потребителей</td></tr><tr><td>Ничего не менять</td><td>Не добавляет новый компонент</td><td>Сохраняет таймауты, повторы и неясную ответственность</td><td>Обратимость высокая, но исходный симптом остаётся</td></tr></tbody></table>\n<p>В этой матрице нет итогового балла. Присвоить «3» за надёжность и «2» за стоимость можно только после определения шкалы, веса и источника данных. Без этого score создаёт видимость точности. Для небольшого решения достаточно назвать обязательное условие, показать trade-off и отметить, какие вопросы ещё не проверены.</p>\n<figure><img src=\"/assets/editorial/2024/adr-decisions-2024-alternatives-matrix.svg\" alt=\"Синтетическая матрица ADR: три варианта сравниваются по ограничению, обратимости, evidence и операционной цене\" loading=\"lazy\" /><figcaption>Схема показывает форму сравнения, а не рейтинг реальных архитектур. Значения в иллюстрации синтетические: их нельзя трактовать как измерение production-нагрузки или стоимости.</figcaption></figure>\n<h2>Запишите выбранный компромисс</h2>\n<p>После сравнения решение должно отвечать на вопрос «что меняем и почему именно сейчас». Важно записать не только положительную сторону. Для status-модели положительный эффект — caller не удерживает один долгий запрос. Отрицательная сторона — сервис теперь владеет жизненным циклом операции, а значит, обязан определить повтор, истечение срока, очистку и поведение при падении worker.</p>\n<pre><code># ADR-0042: асинхронный экспорт с локальным status\n\n## Status\nAccepted\n\n## Context and Problem Statement\nЭкспорт может выйти за временную границу HTTP-запроса. Повтор\nзапроса должен быть безопасным, а запись операции — удаляемой.\n\n## Decision Drivers\n- подтверждение создания отдельно от завершения;\n- идемпотентный повтор;\n- явные expiry и owner состояния;\n- возможность ограниченного возврата.\n\n## Considered Options\n1. Синхронный ответ.\n2. Очередь и status у сервиса экспорта.\n3. Общий workflow между сервисами.\n\n## Decision Outcome\nВыбираем очередь и status у сервиса экспорта: граница владения\nостаётся локальной, а caller получает отдельный контракт состояния.\n\n## Consequences\nПлюс: долгую работу можно наблюдать и повторять безопасно.\nМинус: нужны хранение, expiry, cleanup, retry policy и метрики.\nНе делаем: не превращаем локальный status в общий workflow.\n\n## Confirmation\nПроверить контракт повторного запроса, переходы status, expiry,\nудаление результата и возврат короткой операции на direct path.</code></pre>\n<p>Это воспроизводимый формат записи, но не готовая реализация очереди. Поля <em>Accepted</em>, <em>expiry</em> и <em>owner</em> не запускают процессы сами по себе. Их смысл должен быть закреплён в правилах репозитория: кто принимает запись, где она хранится, как связана с кодом и что считается подтверждением.</p>\n<h2>Проверьте отрицательный путь, а не только happy path</h2>\n<p>У решения есть границы «не делаем». Они защищают локальный механизм от незаметного расширения. В нашем примере сервис экспортов отвечает за собственные операции. Он не становится координатором платежей, уведомлений и нескольких downstream-сервисов. Если появился caller, которому нужен общий status процесса, это новое архитектурное условие, а не повод дописать исключение в старом ADR.</p>\n<p>Для каждой границы задайте обратный путь. Что остановит новые записи? Как найти незавершённые операции? Сколько времени хранить status? Что увидит клиент, если worker завершил файл, но не записал финальный статус? Ответы должны быть в контракте и runbook, а в ADR достаточно зафиксировать решение, цену и ссылки на эти проверки.</p>\n<table><caption>От симптома к проверке и действию</caption><thead><tr><th scope=\"col\">Симптом</th><th scope=\"col\">Гипотеза</th><th scope=\"col\">Проверка</th><th scope=\"col\">Действие</th></tr></thead><tbody><tr><td>Один idempotency key даёт два задания</td><td>Ключ проверяют после постановки в очередь</td><td>Повторить запрос на тестовом хранилище и сравнить созданные ids</td><td>Перенести дедупликацию к границе создания и добавить регрессионный тест</td></tr><tr><td>Status показывает <code>done</code>, а файл недоступен</td><td>Переход статуса не связан с публикацией результата</td><td>Проверить порядок записи результата и смены status при сбое</td><td>Сделать переход атомарным для контракта или описать промежуточное состояние</td></tr><tr><td>Старые операции растут без ограничения</td><td>Expiry записан в ADR, но нет владельца cleanup</td><td>Найти job, журнал удаления и тест срока хранения</td><td>Назначить owner и не считать retention выполненным без evidence</td></tr><tr><td>Новый сервис использует локальный status</td><td>Граница применимости не стала частью контракта</td><td>Проверить список consumers и требования к общему процессу</td><td>Остановить расширение и создать successor ADR при новом scope</td></tr></tbody></table>\n<h2>Подтвердите связь записи с кодом</h2>\n<p>Confirmation — это не фраза «всё проверено», а конкретный способ сравнить принятое решение с реализацией. Для учебного ADR достаточно проверить наличие ключевых разделов и затем отдельно запустить тесты контракта. Команда ниже не требует стороннего инструмента и возвращает ненулевой код, если в файле пропущен обязательный заголовок.</p>\n<pre><code>set -eu\nadr='docs/decisions/ADR-0042-export-status.md'\ntest -s \"$adr\"\ngrep -q '^## Status$' \"$adr\"\ngrep -q '^## Context and Problem Statement$' \"$adr\"\ngrep -q '^## Decision Drivers$' \"$adr\"\ngrep -q '^## Considered Options$' \"$adr\"\ngrep -q '^## Decision Outcome$' \"$adr\"\ngrep -q '^## Consequences$' \"$adr\"\ngrep -q '^## Confirmation$' \"$adr\"\nprintf '%s\\n' 'ADR structure: OK'</code></pre>\n<p>Запустите эту проверку из корня репозитория после создания файла и замените путь на свой. Она проверяет только структуру Markdown. Она не доказывает идемпотентность, безопасность, latency, корректность миграции или наличие owner. Для этого нужны публичные тесты, review кода, нагрузочная проверка, threat model или операционный dry run — в зависимости от решения.</p>\n<h2>Сохраните историю и условие пересмотра</h2>\n<p>Accepted означает, что команда приняла решение в указанном контексте. Это не обещание, что оно навсегда верно, и не сигнал для автоматического запуска работ. Если предпосылка изменилась, не переписывайте старый record так, будто он изначально описывал новый путь. Создайте новую запись, свяжите её с предыдущей и укажите, какой факт изменил выбор.</p>\n<p>Статусы тоже требуют локального соглашения. <em>Proposed</em> может означать «готово к обсуждению», <em>Accepted</em> — «принято», <em>Rejected</em> — «рассмотрено и отклонено», <em>Superseded</em> — «заменено новой записью». Названия встречаются в официальных шаблонах, но workflow approval, даты и права на изменение задаёт конкретная команда.</p>\n<p>Сигнал пересмотра лучше формулировать как наблюдаемое условие: появился consumer, требующий общего status; retention превысил согласованный предел; contract upstream изменился; worker не укладывается в заданный класс операций. Дата review может напомнить о проверке, но сама по себе не означает, что решение устарело. У сигнала должен быть владелец и действие, иначе это пожелание.</p>\n<pre><code>## Reassessment\nОткрыть ADR-0043, если выполняется хотя бы одно условие:\n- второй сервис требует читать status как общий workflow;\n- изменился контракт повторного запроса;\n- retention больше согласованного срока;\n- cleanup не подтверждён тестом или операционным журналом.\nДействие: сравнить локальный status, общий workflow и прямой путь;\nстарую запись не редактировать.</code></pre>\n<h2>Рабочий порядок для команды</h2>\n<ol><li><strong>Сузьте вопрос.</strong> Назовите один значимый выбор, конкретную границу и затронутый контракт.</li><li><strong>Отделите факт от гипотезы.</strong> Запишите симптом и ссылку на источник; неподтверждённое вынесите в evidence gap.</li><li><strong>Назовите цену ошибки.</strong> Укажите, какие данные, время, права или обязанности будут потеряны при неверном пути.</li><li><strong>Соберите доступные варианты.</strong> Приведите их к одному уровню и добавьте «ничего не менять», если такой путь реален.</li><li><strong>Сравните по одинаковым drivers.</strong> Проверьте constraint fit, обратимость, операционную цену, владельца и остаточные риски.</li><li><strong>Запишите decision и отрицательный путь.</strong> Ясно укажите, что команда делает и чего намеренно не делает.</li><li><strong>Назначьте confirmation.</strong> Свяжите решение с тестом, контрактом, benchmark, threat model, runbook или другим проверяемым артефактом.</li><li><strong>Определите reassessment.</strong> Запишите наблюдаемый сигнал, владельца и действие; не полагайтесь на одну календарную дату.</li><li><strong>Сверьте ADR с реализацией.</strong> Если код выбрал другой вариант, исправьте расхождение или оформите новый decision, не переписывая историю.</li></ol>\n<h2>Ограничения применимости и критерий готовности</h2>\n<p>ADR полезен для значимых решений: границ компонентов, API и data contracts, зависимостей, non-functional requirements, способов миграции и технологических направлений. Для тривиального локального рефакторинга отдельная запись может создать больше шума, чем контекста. Порог значимости определяется командой; важно сделать его явным.</p>\n<p>ADR не является design document, threat model, benchmark, тестовым раннером, runbook или approval-системой. Он не гарантирует, что команда нашла все альтернативы, и не превращает отсутствие данных в доказанную безопасность. Для персональных данных, security, legal и recovery нужны профильные проверки с отдельными владельцами. Не публикуйте в открытом ADR секреты, персональные данные и внутренние ссылки без разрешённого доступа.</p>\n<p>Запись можно считать готовой, если читатель без поиска по переписке отвечает на пять вопросов: какой симптом наблюдали; какое условие нельзя нарушить; почему отклонили альтернативы; какую цену принимает выбранный путь; какой сигнал откроет следующий ADR. Если не назван owner состояния или не существует проверка отрицательного пути, документ ещё фиксирует намерение, а не устойчивое решение.</p>\n<h2>Проверяемые источники</h2>\n<ul><li><a href=\"https://adr.github.io/\" target=\"_blank\" rel=\"noopener noreferrer\">Architectural Decision Records — ADR GitHub Organization</a> — официальное определение архитектурного решения, ADR как записи одного выбора и rationale, а также decision log с trade-offs и consequences.</li><li><a href=\"https://github.com/architecture-decision-record/architecture-decision-record/blob/main/locales/en/templates/decision-record-template-by-michael-nygard/index.md\" target=\"_blank\" rel=\"noopener noreferrer\">Template by Michael Nygard — architecture-decision-record</a> — официальный репозиторий с полями Status, Context, Decision и Consequences.</li><li><a href=\"https://github.com/adr/madr/blob/4.0.0/template/adr-template.md\" target=\"_blank\" rel=\"noopener noreferrer\">MADR 4.0.0 ADR template</a> — официальный тегированный шаблон с decision drivers, considered options, decision outcome, consequences и confirmation; структура шаблона не является обязательной политикой каждой команды.</li><li><a href=\"https://docs.aws.amazon.com/prescriptive-guidance/latest/architectural-decision-records/adr-process.html\" target=\"_blank\" rel=\"noopener noreferrer\">AWS Prescriptive Guidance: Architectural decision record process</a> — официальное описание области применения ADR, минимальных разделов и подхода с новой записью, superseding предыдущую при изменении решения.</li></ul>"
}