Хореография vs оркестрация: кто знает бизнес-процесс

Многошаговый процесс между сервисами координируют двумя способами: хореография — сервисы реагируют на события, единого дирижёра нет, поток эмерджентный; оркестрация — центральный координатор явно ведёт шаги и обрабатывает отказы. Разница не в технологии, а в том, ГДЕ живёт знание о процессе: размазано по сервисам или собрано в одном месте. Разбираем компромиссы, антипаттерны (event-спагетти vs god-оркестратор), когда что и почему реальные системы почти всегда гибрид

Как только бизнес-операция расходится на несколько сервисов — оформить заказ = списать оплату, зарезервировать товар, создать доставку, уведомить клиента — встаёт вопрос: кто дирижирует этой последовательностью? Есть ровно два ответа, и выбор между ними определяет, будет ли система понятной через год. Хореография — дирижёра нет: каждый сервис реагирует на события и публикует свои, поток складывается сам. Оркестрация — есть центральный координатор, который явно говорит, что делать дальше. Технологически оба реализуемы; разница глубже — в том, где живёт знание о процессе: размазано по всем участникам или собрано в одном месте.

Часть серии «Событийные паттерны». Тесно связана с Saga (у которой есть обе разновидности) и Temporal (движок оркестрации). Эта статья — про сам выбор координации и его цену.

Слева фигуры в круге реагируют на сигналы соседей паутиной пунктиров без дирижёра (хореография); справа один дирижёр на подиуме направляет шеренгу упорядоченными стрелками (оркестрация)

В статье

Два способа координации

Возьмём «оформить заказ»: оплата → резерв товара → доставка → уведомление. Один и тот же процесс собирается принципиально по-разному.

Хореография — сервисы подписаны на события, реагируют и публикуют свои. Единого сценария нет нигде, поток эмерджентный:

Заказ  ──publish── OrderPlaced
Оплата ──on OrderPlaced──→ списать ──publish── PaymentTaken
Склад  ──on PaymentTaken──→ зарезервировать ──publish── StockReserved
Доставка ──on StockReserved──→ создать ──publish── ShipmentCreated
Уведомл. ──on ShipmentCreated──→ отправить письмо

Оркестрация — центральный координатор (saga-оркестратор, Temporal-workflow) явно вызывает шаги по порядку и решает, что дальше:

OrderOrchestrator:
    take_payment()          → ok
    reserve_stock()         → ok
    create_shipment()       → ok
    notify_customer()       → ok
    (при ошибке любого шага — явная компенсация предыдущих)

Технологически оба реальны: хореография — это чистый pub/sub на Kafka/NATS; оркестрация — движок вроде Temporal или Camunda. Но выбор между ними — не про технологию.

Где живёт знание о процессе

Единственный по-настоящему важный вопрос: где записано знание о том, как устроен весь процесс?

  • Хореография: знание размазано. Каждый сервис знает только «на какое событие я реагирую и какое публикую». Целиком последовательность «оплата → резерв → доставка → уведомление» не описана нигде — она существует только как эмерджентное поведение системы. «Весь поток» не видит никто.
  • Оркестрация: знание собрано в координаторе. Процесс читается как сценарий в одном файле: вот шаги, вот порядок, вот что при ошибке. Есть одно место, куда смотреть и что менять.

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

Хореография: за и против

За:

  • слабая связанность — сервисы не знают друг о друге, только о событиях; publisher не в курсе, кто и сколько его слушает;
  • автономность — команда меняет свой сервис, не согласуя с «дирижёром»;
  • лёгкое расширение — добавить нового потребителя события = подписаться, не трогая существующих (новый сервис аналитики на OrderPlaced — и всё);
  • нет центрального SPOF/бутылочного горлышка — координатора, который может лечь, просто нет.

Против:

  • непрослеживаемость — «никто не знает весь поток»; чтобы понять, что происходит после OrderPlaced, надо обойти все сервисы и посмотреть, кто на что подписан;
  • тяжёлая отладка/мониторинг — вопрос «на каком шаге застрял заказ 42» не имеет одного места для ответа;
  • риск циклических зависимостей — событие A триггерит B, B публикует C, C снова триггерит A; в хореографии это легко не заметить;
  • размазанные компенсации — откат при ошибке (вернуть деньги, снять резерв) тоже приходится собирать из реакций разных сервисов;
  • трудно менять поток — вставить шаг в середину значит переподписать события у нескольких сервисов.

Оркестрация: за и против

За:

  • явный поток — вся последовательность в одном месте, читается как код, меняется в одном файле;
  • простой мониторинг/отладка — статус процесса виден в координаторе: «заказ 42 на шаге create_shipment, ретрай 2»;
  • централизованная обработка ошибок и компенсаций — откат описан там же, где прямой ход (прямой мостик к Saga);
  • проще эволюция сложного процесса — вставить/переставить шаг = правка в одном сценарии.

Против:

  • связанность с координатором — он «знает» про все сервисы, которые вызывает; это осознанная централизация знания;
  • риск бутылочного горлышка/SPOF — координатор на критическом пути (смягчается durable-движками вроде Temporal, которые переживают падения);
  • соблазн разрастись — координатору легко начать тянуть в себя бизнес-логику сервисов (см. god-оркестратор ниже).

Сравнение по осям

Ось Хореография Оркестрация
Знание о процессе размазано по сервисам собрано в координаторе
Связанность слабая (только события) координатор связан со всеми
«Весь поток» виден нигде в одном месте
Мониторинг/отладка тяжело (обход сервисов) легко (статус в координаторе)
Добавить потребителя тривиально (подписка) правка сценария
Изменить порядок шагов тяжело (переподписка) легко (один файл)
Компенсации размазаны централизованы
SPOF нет центрального координатор (смягчается durable)
Циклы/спагетти легко получить незаметно видны в сценарии

Ни одна колонка не выигрывает целиком — это компромисс между автономностью и наблюдаемостью, а не «лучше/хуже».

Антипаттерны: event-спагетти и god-оркестратор

Каждый подход, доведённый до крайности, вырождается в свой антипаттерн.

Event-спагетти (хореография ушла вразнос): события триггерят события, которые триггерят события, и цепочку никто не может проследить. Отладка превращается в археологию — «а кто вообще опубликовал это событие и почему заказ обработался дважды?». Признак: чтобы понять один бизнес-процесс, разработчик открывает пять репозиториев и рисует граф подписок на бумаге.

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

Граница проста: оркестратор решает что и в каком порядке делать; как делать — остаётся внутри сервисов.

Как это ложится на Saga и Temporal

Этот выбор — не абстракция, он живёт внутри знакомых паттернов.

  • Saga бывает двух разновидностей: choreography-based saga (сервисы реагируют на события саги, компенсации размазаны) и orchestration-based saga (оркестратор ведёт сагу и её компенсации). Это ровно тот же выбор — внутри одного паттерна распределённой транзакции. Практическую сторону обеих разбирает Saga на практикеготовится, с 18 августа.
  • Движки оркестрации: Temporal, Camunda/Zeebe — их сама суть в централизации знания о процессе (workflow как код). Чистый pub/sub на Kafka/NATS — это хореография по умолчанию.
  • Идемпотентность нужна обеим: и в хореографии, и в оркестрации шаги ретраятся и события задваиваются — консьюмеры обязаны быть идемпотентными (см. гарантии доставки и идемпотентность).

Лунная база: аварийный протокол двумя способами

Разгерметизация модуля запускает протокол: оценить критичность → зарезервировать аварийный кислород → переключить энергоснабжение → отправить робота → уведомить оператора.

Хореографией: датчик публикует DepressurizationDetected; кислородная подсистема реагирует и публикует EmergencyOxygenReserved; энергоузел реагирует на него и публикует PowerRerouted; и так далее. Плюс — подсистемы автономны, добавить новую реакцию (например, аварийное освещение на DepressurizationDetected) можно, не трогая остальные. Минус — когда протокол «завис», непонятно, на каком шаге: знание о полной цепочке не собрано нигде, а в аварийном сценарии это дорого.

Оркестрацией: EmergencyOrchestrator явно ведёт шаги, ждёт подтверждения каждого с таймаутом, при провале — эскалация на оператора, при отказе — компенсация. Весь протокол читается в одном месте, статус («шаг 3, ждём подтверждения энергоузла, таймаут 5 мин») виден сразу. Для критичного процесса с компенсациями и человеком в цикле это перевешивает — поэтому аварийный протокол оркестрируют, а не пускают на самотёк реакций.

Разный характер процесса → разный выбор координации. Это подводит к главному правилу.

Когда что и почему гибрид

Выбирай хореографию, когда: поток простой и коротких шагов, важна автономность команд, идёт fan-out реакций (одно событие → много независимых потребителей), приоритет — слабая связанность, а сквозная наблюдаемость не критична.

Выбирай оркестрацию, когда: процесс многошаговый с компенсациями, нужна видимость и контроль хода, есть таймауты и человек в цикле, длинные процессы (часы/дни) — здесь durable-движок вроде Temporal снимает и SPOF, и ручной state machine.

Гибрид — это реальность, а не компромисс от бессилия. Зрелые системы оркестрируют критичную бизнес-транзакцию (оплата заказа, аварийный протокол) и хореографируют периферию вокруг неё (уведомления, аналитика, аудит, индексация). Ключевое: выбор делается на процесс, а не на всю систему. Один и тот же сервис может быть шагом оркестрированной саги и одновременно публиковать события, на которые независимо реагирует аналитика. Как это соотносится с остальными событийными паттернами — в матрице решенийготовится, с 8 августа.

Checklist: хореография или оркестрация

  1. Нужно ли видеть «весь поток» в одном месте и отвечать на «на каком шаге застряло»? → скорее оркестрация.
  2. Есть ли компенсации/откат при частичном сбое? → скорее оркестрация (централизованные компенсации).
  3. Процесс длинный, с таймаутами и человеком в цикле? → оркестрация на durable-движке.
  4. Приоритет — автономность команд и лёгкое добавление потребителей? → скорее хореография.
  5. Это fan-out (одно событие → много независимых реакций)? → хореография.
  6. Рискуете ли вы уже сейчас получить event-спагетти (цепочки, которые никто не проследит)? → добавьте оркестрацию в ядро.
  7. Не разрастается ли координатор в god-оркестратор (тянет бизнес-логику)? → верните логику в сервисы.
  8. Решение принято на конкретный процесс, а не на всю систему разом?

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

Демо и версии

  • Demo: digital-cookbook/architecture/event-coordination — один сценарий «оформить заказ» двумя способами: (1) хореография через in-process event-bus — сервисы реагируют на события и публикуют новые, без внешнего брокера (стенд намеренно самодостаточен: в реальной системе на этом месте были бы Kafka/NATS); (2) оркестрация через координатор (Temporal-workflow). Наглядно: где виден «весь поток», как отлаживать «застрявший» заказ, как добавить шаг в каждом варианте, как выглядит компенсация при сбое.
  • Версии движка (Temporal) и зависимостей пиновать при написании; при переносе хореографии на реальный брокер (Kafka/NATS) пиновать и его.

Документация и первоисточники

Обсуждение в Telegram

Присоединиться →

Комментарии