Процессы и формальные методы: BPMN, workflow engines, TLA+
Прошлый урок закончился на границе: конечный автомат описывает жизнь одной сущности, а что делать с процессом, в котором участвуют несколько сервисов, людей и таймеров? Финальный урок раздела поднимается на две верхние ступени лестницы. Сначала модели процессов, которые исполняются: BPMN и workflow-движки, превращающие сагу из урока про надёжную доставку в код, который переживает рестарты. Потом модели, которые доказывают: TLA+ и его родня, инструменты для случаев, когда инвариант дороже месяца работы. Обе ступени дорогие, и половина урока про то, когда на них не подниматься.
Triple Crown: BPMN, CMMN, DMN
В мире бизнес-процессов есть своя тройка стандартов OMG, которую называют Triple Crown. Главный из них BPMN: процесс как граф из событий (круги), задач (прямоугольники), шлюзов-развилок (ромбы) и дорожек участников. Возврат билета в афише на языке BPMN: событие «поступил запрос на возврат», задача «проверить политику возврата», шлюз «больше десяти дней до сеанса?», ветка с полным возвратом, ветка с частичным, дорожки кассира, биллинга и почтового сервиса. Сила нотации в том, что эту диаграмму одинаково читают аналитик, разработчик и операционный директор: BPMN это lingua franca между бизнесом и кодом, отрисованный процесс становится предметом спора до реализации.
Слабость симметрична силе, и ты её уже знаешь из прошлого урока: диаграмма, живущая отдельно от кода, врёт молча. У BPMN есть два честных режима существования. Либо диаграмма это документация, и тогда она обязана жить в репозитории и обновляться через ревью, как любая модель. Либо диаграмма это сам процесс: BPMN 2.0 имеет исполняемую семантику, и движки вроде Camunda выполняют XML диаграммы напрямую. Третий режим, «нарисовали в начале проекта и повесили на стену», производит уверенно врущий артефакт.
Два младших брата короче. CMMN задумывалась для процессов без жёсткого сценария (расследование инцидента, сложная заявка в поддержке): не последовательность, а набор активностей с условиями доступности. Идея здравая, стандарт не взлетел, даже Camunda свернула его поддержку; знай, что термин означает, и решай такие задачи проще. DMN наоборот тихо полезен: это таблицы решений, вынимающие бизнес-правила из if-else в артефакт, который бизнес сам читает, правит и версионирует. Та самая политика возврата:
| # | Дней до сеанса | Тип билета | Возврат |
|---|---|---|---|
| 1 | больше 10 | любой | 100% |
| 2 | от 3 до 10 | обычный | 50% |
| 3 | от 3 до 10 | акционный | 0% |
| 4 | меньше 3 | любой | 0% |
Правила меняются маркетингом раз в сезон, и каждое изменение таблицы вместо изменения кода это сэкономленный релиз. Движки исполняют DMN напрямую, но паттерн ценен и без движка: таблица решений как данные плюс маленький интерпретатор.
Workflow engines: durable execution
Теперь главная технология урока. Вспомни, чего стоила сага из урока про надёжную доставку: статус-машина в базе, outbox, идемпотентные шаги, ретраи с backoff, таймеры на «оплата не пришла за сутки», восстановление после рестарта воркера. Заметь: из всего списка бизнес-логикой были только сами шаги, остальное это инфраструктурный код, одинаковый в каждом проекте. Класс движков под общим лозунгом durable execution забирает этот код себе.
Самый известный представитель: Temporal (внук той же идеи, что AWS Simple Workflow и Cadence из Uber). Модель такая: ты пишешь workflow как обычную функцию, а движок записывает в журнал каждое её взаимодействие с внешним миром. Упал воркер: движок поднимает функцию на другом и перепроигрывает журнал до места падения, после чего исполнение продолжается, будто ничего не было. Заказ из урока про доставку как Temporal-workflow:
export async function orderWorkflow(order: OrderInput): Promise<void> {
// activity: вызов наружу; ретраи и таймауты декларирует движок
await acts.reserveSeats(order);
const paid = await acts.chargePayment(order);
if (!paid) {
await acts.releaseSeats(order); // компенсация: явная, как в саге
return;
}
await acts.sendTickets(order);
// durable-таймер: переживёт деплой, рестарт и месяц ожидания
await sleep('24 hours');
await acts.sendFeedbackRequest(order);
}
Сравни с ручным оркестратором: ни статус-машины, ни таблицы
прогресса, ни крона, который подбирает зависшие заказы. Состояние
процесса это позиция в коде, и она надёжно хранится движком.
sleep на сутки не держит процесс: воркер свободен, движок
разбудит workflow по таймеру, хоть через год.
Бесплатным это не бывает, цена называется детерминизм. Перепроигрывание работает, только если код workflow при повторном исполнении принимает те же решения: внутри workflow запрещены прямые HTTP-вызовы, случайности, чтение часов и всё недетерминированное. Любой side effect уезжает в activity, которую движок исполняет с гарантией at-least-once. Узнаёшь гарантию? Activities обязаны быть идемпотентными, ровно как шаги саги: движок забрал у тебя оркестрацию, но идемпотентность и компенсации остались твоими. Вторая статья цены: версионирование. Workflow живут неделями, и деплой нового кода встречается с журналами, записанными старым; у движков есть механика патчей, но думать о совместимости версий процесса придётся тебе. Третья: движок это stateful-инфраструктура со своей базой, которую надо эксплуатировать (или покупать как облако).
Соседи по нише расставляют акценты иначе. Camunda 8 исполняет BPMN
напрямую: процесс рисует и меняет аналитик, код подписывается на
задачи как worker; выбирай её там, где диаграмма и есть язык
заказчика. Restate журналирует обычные функции и операционно легче
Temporal (один бинарник вместо кластера с базой). Особняком стоят
управляемые облачные сервисы вроде AWS Step Functions, и их водораздел
с Temporal не в надёжности (durable execution и восстановление после
отказа есть у обоих), а в том, чем ты описываешь граф процесса.
Temporal даёт императивный код: workflow это обычная функция,
ветвления и циклы пишутся через if и for, как в листинге выше.
Step Functions предлагает декларативное описание на JSON (язык
Amazon States Language): ты перечисляешь состояния и переходы между
ними, а саму работу всё равно делают твои функции (Lambda), которые
сервис вызывает по этому графу. Camunda с её BPMN из той же породы
декларативных: процесс задан схемой, не кодом. Декларативную модель
легче нарисовать, провалидировать и показать аналитику, но ветвистую
логику на JSON или диаграмме выражать тяжелее, чем кодом; императивный
код выразительнее, зато весь процесс не виден одной картинкой. Выбор
тут про язык описания, не про гарантии. Критерий выбора
самого подхода: движок окупается на процессах в дни и недели, с
человеческими шагами, таймерами и компенсациями. Процесс из двух
шагов с ретраем закрывается очередью из
урока про messaging, и тащить
под него кластер Temporal значит купить экскаватор для клумбы.
дальше после входа
Урок целиком открыт всем, кто вошёл. Войди по своему email, чтобы читать дальше.
Вход пока по приглашению. Если хочешь учиться, напиши Vassiliy.