8 lines
21 KiB
JSON
8 lines
21 KiB
JSON
{
|
||
"index": 118,
|
||
"slug": "editorial-2024-09-field-adr-decisions",
|
||
"title": "ADR устарел: как проверить решение и не переписать его историю",
|
||
"excerpt": "Старый ADR не становится неверным только потому, что изменился код. Разбираем drift, successor-запись, confirmation и безопасный переход от Accepted к Superseded.",
|
||
"contentHtml": "<p>Проблема в актуальности ADR проявляется, когда репозиторий открывают перед изменением сервиса. В записи зафиксирован синхронный экспорт: клиент ждёт ответ с готовым файлом. В коде уже появился worker, а endpoint возвращает <code>jobId</code> и состояния <code>pending</code>, <code>completed</code> и <code>failed</code>. Возникает соблазн заменить в старом файле пару слов и оставить прежнюю дату.</p>\n<p>Такой diff скрывает две разные операции. Первая — уточнить evidence: решение всё ещё подходит, а наблюдение или ссылка стали точнее. Вторая — изменить архитектурную границу: теперь клиент управляет длительной операцией и должен уметь повторно получить её результат. Во втором случае правка старой записи стирает причину прежнего выбора и лишает команду точки сравнения. Разберём, как отличить эти случаи и оформить successor — новую запись, которая заменяет прежнюю.</p>\n<h2>ADR хранит решение в его контексте</h2>\n<p>Architectural Decision Record — запись одного архитектурно значимого решения. Официальный сайт ADR описывает три опорные идеи: решение отвечает значимому требованию, сохраняет rationale и показывает trade-offs и последствия. Это не снимок текущего кода и не обещание, что выбранный вариант навсегда останется лучшим.</p>\n<p>Практический минимальный каркас выглядит так: <code>Status</code>, <code>Context</code>, <code>Decision</code> и <code>Consequences</code>. В Context попадает проблема и условия выбора. Decision называет выбранную границу. Consequences показывает, что стало легче, а что — дороже или рискованнее. MADR 4.0.0 расширяет каркас decision drivers, рассмотренными вариантами и способом confirmation — подтверждения того, что реализация соответствует записи.</p>\n<p>Из этого следует полезное разделение. ADR фиксирует rationale. Исходный код и контракт показывают, что система делает сейчас. Тест, запрос к метрике или результат review отвечает на конкретный вопрос о соответствии. План изменения описывает rollout, наблюдение и rollback. Ссылка из ADR на файл не заменяет ни проверку, ни план.</p>\n<h2>Drift не равен устаревшему решению</h2>\n<p>Сначала выпишите исходное условие, а не название технологии. В нашем примере оно звучит так: «маленький экспорт завершается в рамках запроса, а вызывающая сторона получает файл сразу». Затем соберите новый факт: «время выполнения превышает допустимое ожидание, поэтому сервер возвращает идентификатор операции, а клиент опрашивает её состояние».</p>\n<p>Если новый факт только уточняет ссылку, владельца или измерение, accepted ADR можно дополнить датированной заметкой по правилам команды. Если изменились требование, assumption или граница ответственности, нужен новый ADR. В нём следует сослаться на старый, описать варианты и объяснить цену перехода. Статус старой записи меняется на <code>superseded</code> только после принятия successor. Это правило статьи — безопасная политика append-only; конкретный проект может выбрать другую процедуру и обязан описать её заранее.</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 синхронный ответ, в контракте <code>jobId</code></td><td>Граница времени и владелец состояния операции</td><td>Сравнить версию контракта, обработчик и retry-путь</td><td>Successor в статусе Proposed</td></tr><tr><td>Ссылка ведёт на удалённый модуль</td><td>Артефакт переехал, но решение не изменилось</td><td>Найти новый устойчивый путь и проверить тот же инвариант</td><td>Датированное обновление evidence</td></tr><tr><td>Появился новый класс данных</td><td>Изменились требования безопасности или хранения</td><td>Проверить threat model, права и срок хранения</td><td>Новый ADR либо отклонённая альтернатива</td></tr><tr><td>При review нет факта о consequence</td><td>Календарная дата подменила проверку</td><td>Назначить owner, источник, среду и критерий опровержения</td><td>Validation task, а не новый статус</td></tr><tr><td>Команда хочет «починить текст» после отката</td><td>Откат реализации перепутан с отменой решения</td><td>Сверить принятый successor и фактический rollback</td><td>Сохранённый Accepted ADR или явный Rejected ADR</td></tr></tbody></table>\n<h2>Четыре вопроса перед созданием successor</h2>\n<p><strong>Что изменилось?</strong> Запишите наблюдаемый сигнал: новый endpoint, лимит времени, обязанность хранить статус, класс данных или исчезнувший владелец. Формула «код стал другим» недостаточна, потому что код мог изменить реализацию внутри прежней границы.</p>\n<p><strong>Какая assumption нарушена?</strong> Assumption — условие, на котором держался выбор. Для синхронного экспорта это может быть верхняя граница времени ответа. Для очереди — наличие владельца retry и понятного срока хранения. Назовите условие числом или проверяемым правилом, если это возможно.</p>\n<p><strong>Какие варианты остаются?</strong> Сравните минимум два реалистичных варианта. Для экспорта это синхронный путь с жёстким лимитом, очередь с видимым статусом или передача работы внешнему сервису. Критерии должны быть связаны с проблемой: время ответа, восстановление после сбоя, права доступа, стоимость сопровождения и обратная совместимость.</p>\n<p><strong>Что подтвердит выбор?</strong> Confirmation должен отвечать на один конкретный вопрос. Например: «контракт позволяет клиенту получить итог после временного сетевого сбоя, не создавая второй экспорт». Укажите тест, запрос или review, среду, владельца и результат, который заставит пересмотреть решение.</p>\n<h2>Воспроизводимый учебный пример</h2>\n<p>Ниже — самодостаточная fixture на Node.js. Она не читает репозиторий и не доказывает свойства реальной очереди. Её задача — сделать правило перехода явным: изменение протокола считается сигналом для successor, но старый Accepted ADR не переводится автоматически.</p>\n<pre><code>node --input-type=module <<'NODE'\nconst oldAdr = Object.freeze({\n id: 'ADR-0012',\n status: 'accepted',\n boundary: 'small synchronous export',\n maxDurationMs: 3000,\n});\n\nconst observedContract = {\n transport: 'job',\n returns: ['jobId', 'pending', 'completed', 'failed'],\n};\n\nconst boundaryChanged =\n observedContract.transport !== 'sync' ||\n !observedContract.returns.includes('jobId');\n\nconst review = {\n signal: boundaryChanged ? 'successor-required' : 'reconfirm',\n oldStatus: oldAdr.status,\n oldAdrRemains: oldAdr.status === 'accepted',\n confirmationQuestion:\n 'Can the caller resume one job and observe its final state?',\n};\n\nconsole.log(JSON.stringify(review, null, 2));\nNODE</code></pre>\n<p>Ожидаемый результат — <code>successor-required</code>, а <code>oldAdrRemains</code> равен <code>true</code>. Это не проверка доступности worker, идемпотентности повторного запроса, авторизации или срока хранения. Для production эти свойства должны появиться в отдельном тесте или change plan. Учебная программа лишь защищает от логической ошибки «новый контракт найден — старую историю можно переписать».</p>\n<figure><img src=\"/assets/editorial/2024/adr-decisions-2024-reassessment-loop.svg\" alt=\"Схема повторной проверки ADR: assumption, сигнал, evidence и выбор между подтверждением решения и successor\" loading=\"lazy\" /><figcaption>Сначала фиксируется сигнал и проверяется исходная assumption. Если граница выдержана, ADR подтверждают новыми данными. Если нет, создают Proposed successor; статус старой записи меняют только после явного принятия нового решения.</figcaption></figure>\n<h2>Как оформить successor без потери истории</h2>\n<p>Создайте новую запись рядом со старой и поставьте ей <code>Proposed</code>. В заголовке назовите решаемую проблему и выбранную границу, а не внутреннее имя очереди. Минимальная структура может выглядеть так:</p>\n<pre><code>status: proposed\nsupersedes: ADR-0012\ndecision-makers: export team\n\n# Return a status for long-running exports\n\n## Context and Problem Statement\nThe synchronous request exceeds the caller timeout.\n\n## Decision Drivers\n- bounded request time\n- resumable result\n- authenticated access to job state\n\n## Considered Options\n- keep synchronous export\n- queue the export and expose job status\n\n## Decision Outcome\nChosen option: \"queue the export and expose job status\".\n\n## Consequences\n- The client handles pending, completed and failed states.\n- The service owns retention, retry and authorization rules.\n\n## Confirmation\nContract test: one job can be polled after a transient client timeout.</code></pre>\n<p>Это пример полей, а не обязательный синтаксис для каждого репозитория. Если проект использует YAML front matter, другую нумерацию или отдельный каталог, сохраните локальный contract. Существенны не названия файлов, а обратная ссылка, граница решения, alternatives, consequences и проверка соответствия.</p>\n<p>После review возможны три результата. При принятии successor старую запись помечают <code>Superseded by ADR-0013</code> и не меняют её исходные Context и Consequences. При отклонении нового варианта старый ADR остаётся Accepted, а причина отказа остаётся в новом Rejected ADR. При недостатке данных обе записи должны честно показать неопределённость: Proposed не является разрешением на rollout.</p>\n<h2>Evidence не превращается в гарантию</h2>\n<p>У evidence есть субъект, область и срок действия. Commit показывает состояние кода в конкретной версии. Контрактный тест показывает допустимые формы запроса и ответа. Метрика показывает измеренное поведение при выбранной нагрузке и окне наблюдения. Review подтверждает согласование, но не заменяет эксплуатационную проверку.</p>\n<p>Нельзя делать следующий скачок без отдельного доказательства: «jobId есть в схеме» не означает, что операция переживает повтор; «ошибок мало» не означает, что recovery безопасен; «ADR связан с PR» не означает, что rollout можно откатить. В ADR полезно писать и отрицательный результат: какой риск не проверен, кто его проверит и какое наблюдение остановит выпуск.</p>\n<p>Для локального поиска можно начать с таких команд, подставив пути своего проекта:</p>\n<pre><code>rg -n '^status:|^supersedes:|^## (Context|Decision|Consequences|Confirmation)' docs/adr\nrg -n 'jobId|pending|completed|failed' src test\ngit diff -- docs/adr src test</code></pre>\n<p>Команды показывают кандидатов для ручного сопоставления. Они не вычисляют архитектурный drift автоматически: совпадение слов не доказывает совпадение смысла, а отсутствие совпадения не доказывает, что решение нарушено.</p>\n<h2>Порядок действий для команды</h2>\n<ol><li>Сохраните копию исходного Accepted ADR и выпишите его Context, Decision, Consequences, assumptions, владельца и ссылки.</li><li>Зафиксируйте один наблюдаемый сигнал и версию артефакта, в котором он обнаружен.</li><li>Сопоставьте исходную границу с кодом, API-контрактом, правами и эксплуатационным маршрутом. Разделите факт, гипотезу и пробел evidence.</li><li>Если граница не изменилась, обновите evidence датированной записью и назначьте следующую проверку. Не создавайте successor только из-за календарной даты.</li><li>Если граница изменилась, опишите минимум два варианта, decision drivers, последствия, стоимость миграции и условия rollback.</li><li>Создайте successor в Proposed, добавьте обратную ссылку и назначьте decision-makers и confirmation.</li><li>Проведите review. Только после принятия свяжите записи и переведите старый ADR в Superseded по правилам проекта.</li><li>Проведите реализацию отдельным change plan: тесты, rollout, наблюдение, stop condition и rollback.</li></ol>\n<h2>Ограничения применимости</h2>\n<p>ADR не является RFC, тестовым раннером, threat model, benchmark, runbook или системой управления изменениями. Официальные шаблоны предлагают язык и поля, но не назначают универсальные сроки review, обязательный набор ролей или порог для создания записи. Статусы <code>Accepted</code>, <code>Rejected</code> и <code>Superseded</code> должны иметь однозначное значение в политике конкретного репозитория.</p>\n<p>Append-only подход снижает риск переписать rationale, но увеличивает число записей и требует навигации между ними. Живой документ может быть удобнее для команды, однако тогда нужны датированные изменения и видимая история. Ни один вариант не спасает от неверного Context или отсутствующего owner. Нельзя объявлять решение корректным по одной ссылке, одной метрике или успешному запуску fixture.</p>\n<p>Для описанного случая критерий готовности таков: видны исходная assumption, новый сигнал, выбранные варианты, consequence, владелец confirmation и результат, который опровергнет решение. Если не хватает хотя бы одного элемента, честный итог — открытая проверка или Proposed successor, а не отредактированная история.</p>\n<h2>Проверяемые источники</h2><ul><li><a href=\"https://adr.github.io/\" target=\"_blank\" rel=\"noopener noreferrer\">ADR GitHub Organization: Architectural Decision Records</a> — определения архитектурного решения, rationale, trade-offs и decision log.</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\">Architecture Decision Record: template by Michael Nygard</a> — поля Status, Context, Decision и Consequences; официальный репозиторий шаблонов ADR.</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 template</a> — зафиксированный шаблон с decision drivers, considered options, decision outcome и confirmation.</li></ul>"
|
||
}
|