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

D-04 · Связь сервисов в рабочем процессе#

Третий тип решения. Варианты не берут с рынка (D-01) и не строят из своих фактов (D-02). Их перечисляет модель: три двоичных выбора дают восемь сочетаний, и больше не бывает. Множество закрыто и полно по построению — вариант нельзя упустить, можно только неверно определить координату. Поэтому разделы «матрица» и «из чего выбирают» здесь слиты в один: перечень вариантов и есть система координат.

Когда этот документ применим. Не «когда процесс пересекает границы сервисов», а когда он пересекает границу, за которой нет общей транзакции. Своя это граница или чужая — неважно: внешний платёжный провайдер даёт такую границу и монолиту. На прогоне E-01 документ понадобился системе, у которой сервисов нет вовсе.

Документ отвечает за саги — процессы, у которых несколько разных шагов у нескольких участников и есть что компенсировать. Это не оговорка мелким шрифтом: восемь ячеек выводятся именно из такой формы процесса, и на процессе другой формы координата определяется, но ничего не значит. Границу применимости задаёт раздел 1.

Решение принимается для каждого такого участка отдельно, а не один раз на систему. Одного применения на систему не бывает; несколько применений одного документа в одном проекте — норма, а не признак ошибки.


1. Что выяснить#

Сначала разделить процесс на участки#

Единица решения — не продуктовый сценарий, а участок между границами. «Оформление заказа» в терминах продукта обычно распадается на несколько участков с разными координатами, и часть из них вообще не попадает в это пространство.

Что делать Как
Выписать шаги процесса и участников каждого Включая внешних, которыми мы не управляем
Отметить границы, за которыми нет общей транзакции Другой сервис, другая организация, внешний провайдер — и два хранилища без общей транзакции, даже внутри одного сервиса
Разрезать процесс по этим границам Каждый кусок — отдельное применение документа
Отбросить куски без таких границ Локальная транзакция в одном модуле — вне пространства восьми ячеек, решать нечего
Отвести куски, не являющиеся сагой См. отвод ниже

Граница проходит там, где нет общей транзакции, а не там, где стоит забор. Сервис, читающий из PostgreSQL и пишущий в Redis, пересекает такую границу внутри себя: частичное выполнение возможно, и никто его не откатит. На прогоне E-02 этот кусок провалился между «граница» и «локальная транзакция» и не был разобран ни как то, ни как другое.

Отбрасывание — не формальность: на прогоне E-01 половина процесса «выбор места → резерв → оплата» оказалась обычной локальной транзакцией, и попытка найти для неё ячейку была бы выдумыванием сложности.

Отвод: один шаг, повторённый многократно, — не сага#

Восемь ячеек описывают процесс из разных шагов у разных участников. Массовая раздача — раскладка поста по миллиону лент, рассылка уведомлений, пересчёт витрин — устроена иначе: шаг один, повторяется до миллионов раз, компенсировать нечего, а «выполнено частично» — нормальное состояние, а не отказ.

Признак Сага Размножение одного шага
Шаги Разные, порядок значим Один, порядок между повторами не значим
Незавершённость Отказ, требует компенсации Штатное состояние, требует продолжения
«Кто отвечает за завершение» Настоящий выбор: дирижёр или каждый сам Не выбор: «дирижёр» — это курсор по списку
Что дорого решить Координация и компенсации Размер порции, место курсора, приоритет длинных, порог отсечения хвоста

Координата у такого куска определяется и ничего не даёт — это третий исход применения документа помимо «дал ответ» и «оказался неприменим». Правая колонка таблицы — настоящие вопросы такого куска, и ни один из них не задаёт ни один из трёх координатных вопросов. Отводить надо сразу: заполнять координаты, чтобы потом решать всё остальное вне метода, — худший из вариантов, потому что выглядит как принятое решение.

Собственного документа под этот класс пока нет; кандидат — решение о производных данных, 96-backlog.md.

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

Ответы двоичные, и каждый закрывает ровно одну ось. Это все степени свободы, которые здесь есть.

Вопрос Ось Ответ
Нужен ли инициатору ответ до того, как процесс завершится? Взаимодействие синхронное · асинхронное
Обязаны ли шаги быть согласованы атомарно — и достижима ли эта атомарность? Согласованность атомарная · итоговая · требуется, но недостижима
Кто отвечает за то, что процесс дошёл до конца — явный дирижёр или каждый участник сам? Координация оркестровка · хореография

Четыре проверочных#

Осей не добавляют — не дают ошибиться в трёх ответах выше. Ошибка в координате здесь дороже, чем неудачное сравнение в других решениях: она уводит в чужую ячейку целиком.

  • Действительно ли инициатору нужен ответ — или ему достаточно идентификатора и уведомления потом? Синхронность по привычке — самая частая ошибка, и она удваивает цену всего остального. Ответов три, и третий обычно не замечают: инвариант, требующий синхронности, может закрываться на другом пути целиком. На прогоне E-02 требование «пользователь видит свой пост немедленно» выглядело требованием синхронной вставки, а оказалось требованием к пути чтения, где нужный механизм и так был обязателен по другой причине. Синхронный участок не отвергли — его не существовало. Прежде чем выбирать между «ждать» и «уведомить потом», спросить, этот ли процесс отвечает за инвариант.
  • Атомарность следует из бизнес-инварианта или её удобно предполагать? Второй по частоте способ загнать себя в дорогую половину пространства.
  • Сколько шагов и сколько участников? При пяти и более хореография перестаёт быть отлаживаемой (ARCH-04).
  • Сколько живёт процесс — миллисекунды, минуты или дни? Дни означают движок или явную машину состояний, а не переменную в коде (ARCH-05).

2. Оси и дисквалификаторы#

Осей ровно три, и они координатные, а не сравнительные. Их нельзя вычеркнуть по правилу «ось, по которой варианты не различаются»: убрав одну, получаем не более простое решение, а неопределённую ячейку.

Дисквалификаторы здесь отсекают не варианты, а сочетания требований — и это прямое следствие закрытого множества. Отвергнуть ячейку нельзя, не отвергнув требования, которые в неё привели.

  • Атомарность при хореографии. Завершённость обязательна, а отвечающего за неё нет; компенсации раскиданы по участникам, и порядок их выполнения не гарантирован никем. Ячейка существует, но в неё попадают, а не выбирают её.
  • Атомарность между сервисами без явного инварианта. Дисквалифицировано не решение, а требование: если атомарность не следует из бизнес-правила, её снимают, и половина пространства отпадает вместе с ней.

Третье значение оси согласованности. Встречается случай, когда атомарность требуется инвариантом и недостижима технически: состояние живёт в чужой системе — у платёжного провайдера, у контрагента, — и наша транзакция его не охватывает. По координате это «итоговая», и ячейка выбирается как для итоговой. Но инвариант при этом не закрыт ничем, и запись обязана сказать это прямо: не «выбрали итоговую согласованность», а «атомарность требуется, недостижима, и вот чем мы её замещаем».

Обязательства следуют не из недостижимости, а из чужой организации. Условие, которое их порождает, — завершение процесса зависит от организации, которой мы не распоряжаемся, — и выполняется оно при любом значении оси согласованности. Третье значение лишь один способ туда попасть; второй встречается чаще: атомарность не нужна вовсе, а шаг всё равно выполняет чужая сторона и вправе выполнить его позже.

Проверка условия — один вопрос: есть ли шаг, который выполняет не наша организация, и ждём ли мы его, чтобы считать процесс завершённым? Ответ «да» включает все три обязательства, как бы ни стояла координата.

Обязательство Зачем
Состояние «неизвестно» существует явно Иначе неопределённость размажется по коду в виде таймаутов и догадок
Сверка с чужой системой — часть системы, а не опция Расхождение никогда не равно нулю (DATA-16)
Процесс разбора расхождений спроектирован заранее Иначе он заводится после первого инцидента, в худший момент

Прежняя редакция говорила иначе, и это была ошибка. До v3.1 обязательства висели на третьем значении, и рядом стояло: «Отличать это от "атомарность не нужна" обязательно: там обязательств нет вовсе». На прогоне U-02 координата была вторая — закон требует сверки, а не одновременности, и атомарность сняли как привычку, — а все три обязательства понадобились: акт сдаёт утилизатор и вправе сдать его в следующем квартале. Признаком была не недостижимость атомарности, а чужая организация в середине процесса. - Хореография при пяти и более участниках — или при пяти и более независимых потребителях одного события. Считать только шаги своего процесса недостаточно: событие в общей шине собирает подписчиков само, наш процесс остаётся двухучастниковым навсегда, а вопрос «что вообще происходит после этого события» становится неотвечаемым. Нет места, где видно, где процесс сейчас; отладка инцидента превращается в сбор трассировки по логам всех участников. Если событие публикуется в общую шину, каталог его потребителей — обязательная часть решения, иначе дисквалификатор не сработает никогда. - Экземпляры процесса есть в любой момент, а состояние — в коде сервиса. Перезапуск теряет процесс. Считается не длительность, а произведение длительности на частоту: если оно больше единицы, выполняющиеся экземпляры есть всегда, и плановый выкат попадает в них гарантированно. Час — не порог: на прогоне E-02 процесс жил около минуты, экземпляров было около сорока, автовыкат шёл несколько раз в день, и состояние в памяти было бы потерей при каждом выкате. Нужна таблица состояний или движок (ARCH-05). - Асинхронность без идемпотентного потребителя. At-least-once — умолчание доставки; без дедупликации по ключу «ровно один раз» не существует (MSG-06, NET-16).

3. Пространство решений и наша ячейка#

Здесь слиты разделы «матрица» и «из чего выбирают». Матрицы из восьми колонок не бывает: заполнять семь колонок, в которых мы заведомо не находимся, — работа впустую. Три ответа определяют ячейку; её не выбирают, в неё попадают.

Названия и трёхбуквенные коды — из Ford, Richards, «Software Architecture: The Hard Parts», гл. 12. Код читается по трём осям: s/a — синхронное или асинхронное взаимодействие, a/e — атомарная или итоговая согласованность, o/c — оркестровка или хореография.

Код Название Взаимодействие Согласованность Координация Чем живётся в этой ячейке
sao Epic Saga синхр. атомарная оркестровка Классическая распределённая транзакция: понятнее всего и дороже всего по доступности — она перемножается по цепочке, а дирижёр держит состояние до конца
sac Phone Tag Saga синхр. атомарная хореография Атомарность без дирижёра: каждый знает следующего. Изменение порядка шагов задевает всех участников
seo Fairy Tale Saga синхр. итоговая оркестровка Дирижёр есть, атомарности не требуется — обычно самая практичная из синхронных
sec Time Travel Saga синхр. итоговая хореография Ни дирижёра, ни атомарности; каждый продвигает процесс сам. Работает при двух-трёх шагах, теряет наблюдаемость при росте
aao Fantasy Fiction Saga асинхр. атомарная оркестровка Дирижёр удерживает распределённое состояние там, где ответы приходят когда придут. Сложно и обычно означает, что атомарность стоит перепроверить
aac Horror Story асинхр. атомарная хореография Дисквалифицирована выше: завершённость обязательна, ответственного нет
aeo Parallel Saga асинхр. итоговая оркестровка Рабочая лошадка длительных процессов: дирижёр виден, шаги идут параллельно, компенсации явные. Цена — движок или своя машина состояний
aec Anthology Saga асинхр. итоговая хореография Самая слабая связность и самая дешёвая по зависимостям; наблюдаемость строится отдельно — без сквозной трассировки процесс невидим

Уйти из ячейки можно, только изменив требование. Это главное практическое следствие закрытого множества: в решениях с рынком неподходящий вариант меняют на другой, здесь менять нечего — восемь ячеек и есть всё, что бывает. Меняется вход.

Хотим уйти Что придётся отпустить
Из атомарной половины Требование атомарности — сначала проверить, инвариант это или привычка
Из синхронной половины Ожидание ответа инициатором: отдать идентификатор и уведомить потом
Из хореографии Слабую связность: появится дирижёр, знающий процесс целиком
Из оркестровки Наблюдаемость в одном месте: её придётся собирать трассировкой

Одна ячейка на всю систему — признак того, что её не выбирали. Оформление заказа и рассылка уведомлений не обязаны жить в одной ячейке и обычно не живут.

4. Эвристики#

Начинать с согласованности, а не со связи. Из трёх координат только одна приходит извне: атомарность задаёт бизнес-инвариант. Две другие выбираем мы. Поэтому первый вопрос всегда «правда ли нужна атомарность» — и чаще всего ответ «нет», после чего отпадает половина пространства вместе с самыми дорогими ячейками.

Ничья между двумя соседними ячейками — брать ту, где есть дирижёр. Наблюдаемость дороже связности: отлаживать хореографию во время инцидента — отдельный жанр, и учиться ему приходится в самый неподходящий момент.

«Нет данных» встречается только в проверочных вопросах — число участников и длительность процесса. Оба считаются по существующему коду и логам за час. В координатных вопросах нехватки данных не бывает: если непонятно, нужна ли атомарность, это не нехватка данных, а неопределённое требование, и возвращаться надо в W0.

Давление «сделаем на событиях, это современно». Событийность — это значение координаты, а не достоинство. Спросить, какой инвариант она обслуживает и кто отвечает за завершение процесса. Если ответа на второй вопрос нет — это aac.

Отложить здесь почти не бывает. Участок либо пересекает границу без общей транзакции, либо нет. Если не пересекает — он вне пространства, и решать нечего. Если пересекает из-за планируемого, но ещё не проведённого разреза — вернуться в D-02 и не выбирать ячейку под разрез, который может не состояться.

5. Запись в ADR#

Поле ADR Чем заполняется
Контекст Процесс: шаги, участники, длительность, частота — числами
Дисквалификаторы Какие сочетания требований отвергнуты и почему; обычно это снятое требование атомарности
Рассмотренные варианты Три ответа раздела 1 и код получившейся ячейки. Полный перечень не копируется
Решение Ячейка и её реализация: чем оркестрируем, какие компенсации, где хранится состояние
Обоснование Почему координаты именно такие; если ячейка неудобна — какое требование решили не отпускать
Последствия Что придётся построить: идемпотентность, дедупликация, трассировка, компенсации, их порядок. Если завершение зависит от чужой организации — три обязательства раздела 2: явное «неизвестно», сверка, разбор расхождений
Условия пересмотра Раздел 6
Метод D-04 v<версия> · шаблон ADR v<версия> — рыночного снимка нет

Исключение из правила «матрица копируется в ADR снимком». Копировать нечего: перечень ячеек не зависит от состояния рынка и не меняется со временем — он следует из трёх двоичных выборов. Снимком копируются три ответа, которые привели в ячейку: именно они и есть условия момента.

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

  • Изменился бизнес-инвариант: атомарность потребовалась там, где её не было, или перестала быть обязательной. Это смена координаты, а не настройка.
  • Число участников процесса выросло: хореография, приемлемая при трёх, не приемлема при шести.
  • Длительность выросла с минут до часов — состояние надо хранить иначе.
  • Инициатор перестал ждать ответа или, наоборот, начал.
  • Процесс перестал пересекать границы сервисов — границы изменились по D-02, и решение больше не нужно. Единственный в наборе случай, когда пересмотр означает удаление решения, а не замену.

Проверка перед тем, как считать решение принятым — пять пунктов в шаблоне. Пятый пункт («у отвергнутых вариантов записана причина») здесь читается как «записано, какое требование не стали отпускать»: отвергается не вариант, а вход.

История версий#

Версия Дата Изменение
3.1 2026-09-22 MINOR, по итогам прогона U-02, правка Р5·A плана ревизии. Три обязательства перепривязаны: они следуют не из недостижимости атомарности, а из того, что завершение процесса зависит от организации вне нашего управления, — и включаются при любом значении оси согласованности. Дана проверка одним вопросом. Снято прежнее утверждение «при "атомарность не нужна" обязательств нет вовсе»: прогон показал обратное — координата вторая, обязательства все три. Третьему значению оставлено то, что принадлежит ему одному: инвариант не закрыт ничем, и запись обязана это сказать. Координаты, оси и дисквалификаторы не изменились — изменился список того, что обязано попасть в запись, поэтому MINOR. Старым ADR: перепроверить один — в U-02 · ADR-0005 обязательства выполнены по существу, но выведены вручную, а не документом; исход не меняется. Остальные ADR по D-04 (E-01, E-02) проверяются одним вопросом: был ли в процессе шаг чужой организации, которого мы ждём
3.0 2026-09-17 MAJOR, по итогам прогона E-02. Область применения сужена явно: документ отвечает за саги, и добавлен отвод для массовой раздачи — одного шага, повторённого многократно. Граница без общей транзакции распространена на два хранилища внутри одного сервиса. Дисквалификатор «дольше часа» заменён на произведение длительности на частоту. Дисквалификатор «пять и более участников» считает также независимых потребителей события в общей шине. У проверочного вопроса об инициаторе назван третий ответ — инвариант закрывается на другом пути. Старым ADR: проверить, не остались ли неразобранными куски между двумя хранилищами одного сервиса; не хранится ли состояние в памяти при постоянно выполняющихся экземплярах; не занята ли ячейка процессом, который на самом деле размножение одного шага
2.0 2026-09-16 MAJOR, по итогам прогона E-01. Условие применимости переформулировано: граница без общей транзакции, а не граница сервисов — документ нужен и монолиту с внешним провайдером. Добавлено третье значение оси согласованности: «требуется, но недостижима», со своим списком обязательств. Добавлен разбор процесса на участки перед применением. Старым ADR: проверить, не было ли участков, пропущенных из-за прежней формулировки применимости, и не записана ли недостижимая атомарность как обычная итоговая — во втором случае могли не появиться сверка и состояние «неизвестно»
1.0 2026-09-16 Первая версия и первый документ третьего типа: 3 координатных вопроса плюс 4 проверочных, 3 координатные оси, 5 дисквалификаторов по сочетаниям требований, слитые разделы «матрица» и «из чего выбирают», таблица выхода из ячейки