Варианты в ADR часто выглядят честно только на заголовках. Выбранный путь занимает страницу деталей, а второй описан фразой «слишком сложно». Через несколько месяцев такой record не помогает: невозможно понять, от чего именно отказались и какую цену приняли. Симптом — в review спорят о вкусе, потому что не видно criteria. Цена ошибки — необратимое изменение может выиграть по скорости первой реализации, хотя проигрывает по ownership, rollback или отсутствующему evidence.
Сравнение не требует притворяться, что инженерный выбор сводится к одному числу. Но оно требует одинаковых вопросов к каждому варианту. Для 2024 года полезна короткая матрица: какой constraint закрывает вариант, насколько он обратим, какое evidence уже есть, какое evidence отсутствует, кто владеет operational cost и что станет сигналом для reassessment. Score допустим как учебная дисциплина или как transparent tie-breaker, если его веса не выдают за данные production и рядом остаётся текстовое объяснение.
Симптом → причина → проверка → действие
- Симптом. Один вариант в ADR называют «простым», другой — «правильным», но не видно, для какого constraint эти слова верны.
- Причина. Alternatives собраны из разных уровней: один описывает implementation, второй — vendor, третий — будущую мечту. Их цену нельзя сравнить.
- Проверка. Для каждого варианта заполните один набор: constraint fit, reversibility, evidence fit, operating cost, owner и explicit downside. Отдельно назовите, что таблица не измеряет.
- Действие. Выберите вариант по записанному compromise, поставьте Proposed, назначьте review signal. Если новый факт меняет assumption, создайте successor ADR и только после acceptance отметьте старый как Superseded.
Четыре criteria, которые стоит назвать явно
Constraint fit отвечает на самый конкретный вопрос: выполняет ли вариант зафиксированное условие сейчас. Для webhook это может быть разделение acknowledgement и business completion; для export — наличие caller-visible status; для cache — видимая freshness bound. Если constraint сформулирован словами «сделать надёжно», оценка неизбежно станет вкусовой. Сначала перепишите constraint в проверяемую форму, затем сравнивайте варианты.
Reversibility не означает «можно отменить любой commit». Она означает, что change имеет ограниченную область, named owner и понятный путь назад. Shared state, новый protocol или cross-team contract обычно расширяют границу отката. Это не автоматический запрет. Но ADR должен назвать эту цену рядом с преимуществом, иначе rollback вспоминают только после incident.
| Criterion | Вопрос к варианту | Нужный артефакт | Что нельзя выводить |
|---|---|---|---|
| Constraint fit | какой declared requirement выполняется и где граница | контракт, problem statement или testable acceptance question | что решение оптимально для всех requirements |
| Reversibility | какой scope возврата, owner и stop condition | rollback outline и link на affected boundary | что rollback уже проверен в production |
| Evidence fit | какой факт поддерживает choice и чего не хватает | source, controlled test plan или explicit unknown | что отсутствие evidence равно безопасному результату |
| Operating cost | кто поддерживает expiry, status, retry, migration или review | owner и consequence в ADR | что cost измерен в деньгах или часах |
| Reassessment | какой signal отменяет assumption и когда вернуться | review date, metric question или code-contract link | что дату кто-то выполнит автоматически |
Скоринговая модель полезна только вместе с её ограничением
Ниже учебная модель использует четыре fixed synthetic числа от 1 до 3. Weights заранее записаны: reversibility умножается на 2, evidence fit — на 3, constraint fit — на 4, operating cost вычитается с весом 1. Для synthetic cache boundary вариант bounded BFF cache получает 23, direct read — 12, shared cross-service cache — 13. Это воспроизводимо: любой reader может запустить fixture и увидеть те же числа. Но результат не даёт права включать cache. В модели нет traffic, data classification, source behaviour, SLO, team skill, logs, interviews или production cost.
import {
createFixedSyntheticAdrInput,
inspectSyntheticAdrDecision,
planSyntheticAdrRecord,
runAdrDecisionsFixture,
} from './upgrade-2024-09.mjs';
const report = inspectSyntheticAdrDecision(
createFixedSyntheticAdrInput('fixed-session-cache-boundary-v1'),
);
const draft = planSyntheticAdrRecord(report);
if (!Object.values(runAdrDecisionsFixture().assertions).every(Boolean)) {
throw new Error('fixed synthetic ADR fixture failed');
}
console.log(report.decision.id); // bounded-bff-cache
console.log(report.decision.score); // 23
console.log(draft.status); // proposed
// Здесь нет read ADR file, source code, Git, CI, сети или production data.
Воспроизводимость здесь проверяет не полезность чисел, а дисциплину контракта. Fixture принимает только case id из embedded records. Она отвергает extra field, похожий на ADR file, repository branch, CI URL, network endpoint или production flag. Report сравнивается с canonical fixed object; подмена decision, score или nested alternative field отклоняется. Sparse array и cyclic JSON тоже не проходят. Благодаря этому учебный пример не превращается в скрытый reader, crawler или decision service.
Как читать score без самообмана
Сначала объясните qualitative compromise. В synthetic cache case локальный bounded cache выигрывает потому, что explicit freshness и rollback остаются у одного owner. Shared cache не «плохой»: у него другая цена — cross-service invalidation и более широкая ownership boundary. Direct read не «наивный»: он может быть верным, если freshness contract или cost boundary не требуют state. Затем покажите score как проверку того, что записанные weights соответствуют уже описанному выбору. Если текст и число спорят, исправлять нужно не число первым, а скрытый constraint или неполную alternative.
Хорошая матрица также хранит evidence gap. Например, для partner webhook можно знать, что acknowledgement и completion должны быть раздельны, но не знать реальную duplicate pattern. Тогда ADR может предложить durable intent with worker как вариант, который соответствует synthetic constraints, и одновременно записать: неизвестны actual delivery semantics, retention, operator path и recovery verification. Такой record честнее, чем «выбрали очередь, потому что она надёжная». Evidence не надо придумывать, но нужно назвать, до какого acceptance или implementation шага оно обязательно.
Status — часть механизма, а не декоративная строка
Proposed означает, что context и alternatives ещё можно изменить после review. Accepted означает, что именно этот record фиксирует принятое решение. Deprecated или Superseded не означают, что прошлое было ошибкой. Они означают, что current context изменился и есть link на successor. Nygard явно предлагает сохранять старый record и помечать его superseded, когда решение reverses. Универсальный процесс здесь не требуется: lifecycle конкретного проекта согласуют отдельно. Здесь важна минимальная invariant: accepted history не переписывают задним числом.
Safe supersede состоит из двух разных действий. Сначала создаётся successor со своим context, alternatives, decision, evidence gap и status Proposed. Он должен ссылаться на old ADR и точно назвать drift: changed source contract, missing owner, changed access boundary или invalidated assumption. Затем люди принимают successor. Только после acceptance old record получает link и status Superseded. Если implementation нужно откатывать, это отдельный change plan. Supersede меняет документационный status, а не совершает rollback за систему.
Матрица не заменяет доказательство
Число нельзя использовать как evidence. Если synthetic score говорит 21, это не p95, не incident rate, не результат experiment и не мнение реального customer. В то же время numeric table может сделать discussion короче: reviewer видит, что cost ownership был учтён, и может спросить ровно о неправдоподобном weight или пропущенном criterion. Когда нужна проверка реальной нагрузки, безопасности или миграции, ADR должен описать method, boundary, owner и stop condition, а не подставлять в таблицу красивое число.
MADR держит рядом drivers, options, outcome, consequences и validation. Choice без driver неясен, consequence без validation не закрывает риск. Если варианты несравнимы или critical evidence отсутствует, ADR остаётся Proposed: сначала исследовательский вопрос, потом implementation.
Порядок сравнения и принятия
- Запишите one decision. Один ADR не должен выбирать и database, и rollout strategy, и access model. Разделите независимые compromises.
- Сформулируйте constraints. Каждое условие должно быть читаемо как вопрос проверки, а не как оценка «современно» или «правильно».
- Соберите одинаковые alternatives. Для каждого назовите mechanism, owner, reversibility, evidence gap и cost. Не скрывайте вариант отказа от change.
- Запишите decision rationale. Фраза должна связать choice с конкретными drivers и назвать price. Score можно приложить как synthetic or agreed model, но не вместо rationale.
- Определите validation. Назовите artifact, owner, scope и stop condition. Если evidence нельзя собрать сейчас, record остаётся Proposed.
- Поставьте reassessment hook. Review date и signal должны быть ближе к assumptions, чем к календарному ритуалу.
Ограничения и следующий проверяемый шаг
Материал не предлагает universal weights, не сравнивает vendors, не выбирает cache, queue или export architecture для чьего-либо продукта. Fixed objects не читают files, Git, network, CI, production, interviews или metrics. Поэтому PASS fixture доказывает только закрытость и canonical shape учебной модели. Он не доказывает, что score честен, что alternative реализуема или что новая policy даст измеримый эффект.
Следующий проверяемый шаг: возьмите ADR, в котором выбранный вариант подробно описан, а отклонённый — нет. Сделайте из него матрицу из четырёх rows: constraint, reversibility, evidence, owner cost. Затем добавьте одну строку «чего не знаем». Если после этого выбор меняется, не исправляйте старый Accepted ADR. Создайте Proposed successor и объясните изменение criterion. Если не меняется, всё равно появится полезный artefact для следующего review.
Историческая граница сентября 2024
Источники закреплены до сентября 2024: датированный snapshot первичного Nygard text, immutable Git snapshot Nygard template и immutable commit MADR 3.0.0. Они поддерживают структуру ADR, alternatives, consequences, validation и передачу rationale, но не дают универсальную score formula и не задают process чужой команды. Все values, weights, outcomes и supersede paths в учебной fixture fixed synthetic in-memory; это не production metric, не interviews, не Git analysis, не CI result и не historical evidence конкретного решения.
Проверяемые источники
- Michael Nygard: Documenting Architecture Decisions, snapshot 22.08.2024 — Датированный снимок первичного текста автора ADR: короткий record с context, decision, status и consequences, а также сохранение старого record при supersede. Snapshot закрепляет историческую версию до сентября 2024; это формат и аргументация, а не policy конкретной команды.
- Nygard ADR template mirror, immutable commit 93f7e465, 18.10.2023 — Неизменяемый Git snapshot template, который явно ссылается на формат Michael Nygard и фиксирует Title, Status, Context, Decision и Consequences. Это cross-check формы, а не первичный текст и не обязательный process команды.
- MADR 3.0.0 template, immutable commit 97fb8ed, 09.10.2022 — Неизменяемый первичный артефакт Markdown ADR, доступный до сентября 2024. В template есть status, date, deciders, decision drivers, options, outcome, consequences и validation. Это пример формы, не обязательный набор полей и не измерение качества решения.