Перейти к содержанию

ADR-0003 · Связь в процессе «резерв → оплата → билет»#

Статус:    принят · 2026-09-16
Метод:     D-04 v1.0 · шаблон ADR v1.1
Участники: команда разработки (4 человека)

Учебная запись: часть прогона E-01, система вымышлена. Форма и рассуждение настоящие.

Контекст#

Вход — 00-context.md. Хранилище — ADR-0001, границы — ADR-0002: модульный монолит.

Сначала пришлось решить, применим ли документ вообще. D-04 говорит: решение принимается для каждого процесса, пересекающего границы сервисов. Сервисов у нас нет — всё в одном процессе. По букве документ неприменим.

По существу — применим, и вот почему: процесс пересекает границу внешнего платёжного провайдера. За этой границей общая транзакция недостижима принципиально, и неважно, что по нашу сторону всё лежит в одном монолите. Значит условие применимости в D-04 сформулировано неверно: дело не в наших сервисах, а в наличии границы, за которой нет общей транзакции.

Процесс распадается на два, и только один попадает в пространство решений#

Участок Участники Попадает ли в пространство D-04
Выбор места → резерв Инвентарь, один модуль, одна транзакция Нет. Локальная транзакция, распределённости не возникает вовсе
Оплата → билет → уведомление Заказы, Инвентарь, Уведомления, внешний провайдер Да

Дальше — только второй участок.

Три координатных вопроса#

Вопрос Ответ Почему
Нужен ли инициатору ответ до завершения? Асинхронное Провайдер отвечает за секунды, хвост уходит в десятки [сторонний: CASE-09]. Удерживать запрос столько в окно старта продаж нельзя; ответ приходит вебхуком, пользователь видит промежуточное состояние
Обязана ли согласованность быть атомарной? Итоговая — вынужденно См. ниже: это третий случай, которого в документе нет
Кто отвечает за завершение процесса? Оркестровка Явная машина состояний платежа создан → авторизован → списан → возвращён, плюс неизвестен (CASE-09)

Ось согласованности потребовала третьего значения#

D-04 предлагает выбор из двух: атомарная или итоговая. Здесь ни то ни другое:

  • атомарность требуется — И3 запрещает списать деньги дважды за попытку, И2 запрещает аннулировать оплаченный билет;
  • атомарность недостижима — деньги живут в системе провайдера, наша транзакция их не охватывает.

Записано как «итоговая», но это не то же самое, что «атомарность не нужна». Из недостижимости следуют обязательства, которых у обычной итоговой согласованности нет: состояние неизвестен обязано существовать, а ежедневная сверка с выпиской провайдера — обязательная часть системы, а не опция (CASE-09, DATA-16).

Дисквалификаторы#

Условие Сработало Что из этого следует
Атомарность при хореографии Нет У нас оркестровка
Атомарность без явного инварианта Неприменимо Инвариант есть; проблема не в его отсутствии, а в недостижимости атомарности
Хореография при пяти и более участниках Нет Участников четыре, координация централизованная
Процесс дольше часа, состояние в коде Да Сам платёж идёт секунды, но состояние неизвестен живёт до суточной сверки. Состояние обязано лежать в таблице, а не в памяти процесса
Асинхронность без идемпотентного потребителя Да Вебхук провайдера приходит at-least-once. Нужна дедупликация по ключу идемпотентности попытки (NET-16, MSG-06)

Два последних не отвергли решение — они превратились в обязательные работы.

Рассмотренные варианты#

Перечень не копируется: восемь сочетаний не зависят от нашего проекта и лежат в D-04. Снимком копируются три ответа, которые привели в ячейку, — они выше.

Ячейка: aeo — асинхронное взаимодействие, итоговая согласованность, оркестровка. В D-04 она названа Parallel Saga; название — метка сочетания координат, а не утверждение, что наши шаги идут параллельно.

Проверено, что мы не в соседних ячейках:

Соседняя Почему не там
seo — синхронное Потребовало бы держать запрос пользователя всё время ответа провайдера, включая хвост в десятки секунд
aao — атомарная Атомарность недостижима, а не отвергнута
aec — хореография Никто не отвечал бы за то, что процесс дошёл до конца; при обязательной сверке это неприемлемо

Решение#

aeo: асинхронно, итоговая согласованность, явный оркестратор.

Оркестратор — машина состояний платежа в модуле Заказы, состояние в таблице. Переходы разрешены явным списком; неизвестен — полноправное состояние, разрешаемое сверкой. Компенсация к неудачной оплате — освобождение резерва; компенсация к ошибочному списанию — возврат средств, а не откат транзакции.

Движок оркестрации не берём: у команды нет опыта, процесс короткий и одного вида. Своя таблица состояний здесь дешевле — но это решение обратимо и подлежит пересмотру при появлении второго-третьего длительного процесса.

Обоснование#

Из трёх координат две определились без обсуждения. Согласованность задана извне — инвариантом и невозможностью его обеспечить транзакцией. Взаимодействие задано поведением провайдера, а не нашим выбором. Выбирали мы только координацию, и здесь довод один: при обязательной сверке нужен кто-то, кто знает, где процесс сейчас; хореография это знание размазывает.

Эвристика D-04 «начинать с согласованности, а не со связи» сработала точно: она первой вывела на вопрос, который оказался главным, — и заодно на то, что документ этого случая не описывает.

Последствия#

Всё, что придётся построить дополнительно, — следствие ячейки, а не отдельные пожелания:

  • Ключ идемпотентности на попытку оплаты; повтор возвращает результат первой (NET-16).
  • Таблица состояний платежа с явными переходами и состоянием неизвестен.
  • Ежедневная сверка с выпиской провайдера и процесс разбора расхождений. Расхождение никогда не равно нулю: доли процента операций требуют разбора [сторонний: CASE-09], и процесс проектируется сразу.
  • Дедупликация вебхуков провайдера по ключу.
  • Outbox для атомарной записи «в БД + событие уведомления» (MSG-07) — иначе двойная запись даст потерянные или задвоенные письма.
  • Промежуточное состояние в интерфейсе: «оплата обрабатывается» — раз взаимодействие асинхронное, пользователю это видно, и продукт обязан это предусмотреть.
  • Резерв не освобождается, пока платёж в состоянии неизвестен — иначе нарушится И2. Значит срок удержания места фактически длиннее срока резерва, и это надо учесть в расчёте доступности мест.

Условия пересмотра#

  • Изменился бизнес-инвариант: например, разрешили овербукинг — тогда атомарность перестаёт требоваться и половина обязательств отпадает.
  • Провайдер начал отвечать синхронно и быстро с гарантией — координата взаимодействия меняется.
  • Появился второй и третий длительный процесс — своя машина состояний перестаёт быть дешевле движка.
  • Участников процесса стало пять и более.
  • Модули разъехались в сервисы (ADR-0002 пересмотрен) — ячейка остаётся той же, но цена ошибки растёт.

Связанные ADR#

Зависит от: ADR-0001, ADR-0002.