DarkRiDDeR12 мин

Как сравнивать альтернативы в ADR и не прятать цену

АрхитектураДокументация

Варианты в ADR часто выглядят честно только на заголовках. Выбранный путь занимает страницу деталей, а второй описан фразой «слишком сложно». Через несколько месяцев такой record не помогает: невозможно понять, от чего именно отказались и какую цену приняли. Симптом — в review спорят о вкусе, потому что не видно criteria. Цена ошибки — необратимое изменение может выиграть по скорости первой реализации, хотя проигрывает по ownership, rollback или отсутствующему evidence.

Сравнение не требует притворяться, что инженерный выбор сводится к одному числу. Но оно требует одинаковых вопросов к каждому варианту. Для 2024 года полезна короткая матрица: какой constraint закрывает вариант, насколько он обратим, какое evidence уже есть, какое evidence отсутствует, кто владеет operational cost и что станет сигналом для reassessment. Score допустим как учебная дисциплина или как transparent tie-breaker, если его веса не выдают за данные production и рядом остаётся текстовое объяснение.

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

  1. Симптом. Один вариант в ADR называют «простым», другой — «правильным», но не видно, для какого constraint эти слова верны.
  2. Причина. Alternatives собраны из разных уровней: один описывает implementation, второй — vendor, третий — будущую мечту. Их цену нельзя сравнить.
  3. Проверка. Для каждого варианта заполните один набор: constraint fit, reversibility, evidence fit, operating cost, owner и explicit downside. Отдельно назовите, что таблица не измеряет.
  4. Действие. Выберите вариант по записанному 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.

Criteria для alternatives: что именно сравниваем
CriterionВопрос к вариантуНужный артефактЧто нельзя выводить
Constraint fitкакой declared requirement выполняется и где границаконтракт, problem statement или testable acceptance questionчто решение оптимально для всех requirements
Reversibilityкакой scope возврата, owner и stop conditionrollback outline и link на affected boundaryчто rollback уже проверен в production
Evidence fitкакой факт поддерживает choice и чего не хватаетsource, controlled test plan или explicit unknownчто отсутствие evidence равно безопасному результату
Operating costкто поддерживает expiry, status, retry, migration или reviewowner и consequence в ADRчто cost измерен в деньгах или часах
Reassessmentкакой signal отменяет assumption и когда вернутьсяreview date, metric question или code-contract linkчто дату кто-то выполнит автоматически
Матрица из трёх синтетических вариантов сравнивает constraint fit, reversibility, evidence fit и operating cost. Выбранная строка сопровождается явно отмеченной ценой и review signal, а подпись указывает, что score не является production metric.
Матрица делает цену выбора видимой. Значения в ней иллюстративны: они не измеряют team velocity, reliability, cost или результат реального внедрения.

Скоринговая модель полезна только вместе с её ограничением

Ниже учебная модель использует четыре 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.

Порядок сравнения и принятия

  1. Запишите one decision. Один ADR не должен выбирать и database, и rollout strategy, и access model. Разделите независимые compromises.
  2. Сформулируйте constraints. Каждое условие должно быть читаемо как вопрос проверки, а не как оценка «современно» или «правильно».
  3. Соберите одинаковые alternatives. Для каждого назовите mechanism, owner, reversibility, evidence gap и cost. Не скрывайте вариант отказа от change.
  4. Запишите decision rationale. Фраза должна связать choice с конкретными drivers и назвать price. Score можно приложить как synthetic or agreed model, но не вместо rationale.
  5. Определите validation. Назовите artifact, owner, scope и stop condition. Если evidence нельзя собрать сейчас, record остаётся Proposed.
  6. Поставьте 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. Это пример формы, не обязательный набор полей и не измерение качества решения.