D-04 · Связь сервисов в рабочем процессе#
- Версия: 3.1 · 2026-09-22 · по мастер-шаблону v6.0
- Известно также как: динамическая связанность, dynamic coupling, выбор саги, «оркестрация или хореография», как сервисы разговаривают
- Этап: W6 · Согласованность и корректность · состав участников приходит из W4
- Стадия: гринфилд · работающая — на E-01 понадобился системе, у которой сервисов нет вовсе
- Профиль: дорого изменить · высокое влияние → полная форма с особенностями третьего типа
- Не решает: где проходят границы — это D-02 в работающей системе и D-05 на старте; делятся ли сами данные — D-07; что делать при отказе участника — это W7; массовую раздачу одного шага — см. отвод в разделе 1
- Не путать с: D-02 — там статическая связанность (кто от кого зависит), здесь динамическая (как разговаривают во время работы)
- Карточки: DIST-08 распределённые транзакции и saga · ARCH-04 хореография и оркестрация · ARCH-05 оркестрация длительных процессов · MSG-06 гарантии доставки · NET-16 идемпотентность
Третий тип решения. Варианты не берут с рынка (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, и решение больше не нужно. Единственный в наборе случай, когда пересмотр означает удаление решения, а не замену.
Проверка перед тем, как считать решение принятым — пять пунктов в шаблоне. Пятый пункт («у отвергнутых вариантов записана причина») здесь читается как «записано, какое требование не стали отпускать»: отвергается не вариант, а вход.
- Источники: Ford, Richards — Software Architecture: The Hard Parts, гл. 12 (модель трёх сил и названия восьми сочетаний) · microservices.io — Saga ·
00-workflow.md, шаг W6
История версий#
| Версия | Дата | Изменение |
|---|---|---|
| 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 дисквалификаторов по сочетаниям требований, слитые разделы «матрица» и «из чего выбирают», таблица выхода из ячейки |