Camunda 7 и BPMN
Camunda 7 — процессный движок, который исполняет BPMN-модель и хранит состояние экземпляров процесса. Для Java-разработчика важно видеть за диаграммой не только бизнес-маршрут, но и вполне конкретные вещи: транзакции, записи в базе, задания job executor, переменные и повторное выполнение кода.
Статус версии
Финальный feature-релиз Camunda 7 Community Edition — 7.24 от октября 2025 года. После него Community Edition больше не получает релизы и security patches. У Enterprise Edition другой срок поддержки. Это не мешает разбираться с работающими системами, но для нового проекта нужно отдельно оценивать жизненный цикл платформы и план миграции.
Модель исполнения
У экземпляра процесса есть:
- определение процесса — версия развернутой BPMN-модели;
- execution — текущее положение токена или нескольких токенов;
- process variables — данные процесса;
- задачи пользователя, jobs, subscriptions и incidents;
- история, объём которой зависит от history level.
Когда внешний вызов запускает процесс, завершает user task или коррелирует сообщение, движок выполняет синхронный участок до следующего wait state. Весь этот участок обычно является одной транзакцией.
внешняя команда
→ выполнение BPMN-элементов
→ wait state
→ сохранение состояния
→ commitЕсли до сохранения состояния возникает необработанное исключение, транзакция откатывается к предыдущей устойчивой точке. Внешние побочные эффекты — например, уже выполненный HTTP-вызов — вместе с базой Camunda автоматически не откатываются.
Wait state и transaction boundary
Wait state — место, где движок сохраняет состояние и ждёт нового триггера. Типичные примеры:
- user task;
- receive task;
- timer, message и signal catch event;
- event-based gateway;
- external task.
asyncBefore и asyncAfter позволяют создать дополнительную границу:
activity A
→ commit
→ job
→ activity BПосле границы продолжение выполняет job executor. Это меняет не только поток выполнения, но и:
- момент commit;
- место rollback;
- число jobs;
- возможность retry;
- конкуренцию нескольких jobs за данные;
- наблюдаемое состояние процесса.
Поэтому «поставить async» — архитектурное решение, а не способ механически ускорить диаграмму.
Service task
В Camunda 7 service task может вызывать Java-код несколькими способами:
<bpmn:serviceTask
id="loadData"
camunda:delegateExpression="${loadDataDelegate}" />Основные варианты:
camunda:class— движок создаёт экземпляр класса;camunda:delegateExpression— выражение находит bean, обычно Spring bean;camunda:expression— вызывает метод или вычисляет значение;- external task — работу забирает отдельный worker;
- connector — интеграционный вызов, если такой подход принят в проекте.
Для Spring-приложения мне проще контролировать зависимости и тестирование через delegateExpression. Сам delegate стараюсь оставлять тонким: получить переменные, вызвать прикладной сервис, записать результат. Бизнес-логику, пригодную без движка, держу в обычных Java-классах.
@Component
public class LoadDataDelegate implements JavaDelegate {
private final DataService dataService;
public LoadDataDelegate(DataService dataService) {
this.dataService = dataService;
}
@Override
public void execute(DelegateExecution execution) {
String itemId = (String) execution.getVariable("itemId");
Result result = dataService.load(itemId);
execution.setVariable("result", result.status());
}
}Переменные процесса как контракт
Переменная — часть интерфейса между BPMN и кодом. Для каждой значимой переменной полезно знать:
| Свойство | Пример |
|---|---|
| имя | approved |
| тип | Boolean, а не строка "true" |
| обязательность | нужна до конкретного gateway |
| область | process scope или local scope |
| источник | старт процесса, delegate, message |
| потребитель | условие, форма, следующий delegate |
| допустимый размер | особенно для текста и JSON |
Проблема «переменная существует» ещё не означает, что у неё ожидаемый тип. Значения, пришедшие через REST, формы, сериализацию или expression language, нужно проверять на границе.
Крупный объект в process variables увеличивает нагрузку на базу и усложняет совместимость версий Java-класса. Часто безопаснее хранить идентификатор или небольшой стабильный DTO, а актуальные данные получать из системы-владельца.
Gateway
Exclusive gateway
Exclusive gateway выбирает одну исходящую ветку. Условия вычисляются по порядку, а при нескольких true будет выбрана первая подходящая ветка в порядке XML — полагаться на это как на бизнес-правило не стоит.
Если ни одно условие не подошло и default flow не задан, движок завершит команду ошибкой. Default flow играет роль else, но не должен маскировать некорректный вход.
approved == true → продолжить
approved == false → отклонить
default → обработать неизвестное значениеParallel gateway
При разветвлении parallel gateway создаёт токен на каждой исходящей ветке. При объединении ждёт токены с требуемых входящих ветвей. Символ параллельности в BPMN не гарантирует одновременные Java threads: без async boundary синхронные ветви могут исполняться последовательно в одном потоке команды.
Inclusive gateway
Inclusive gateway может выбрать несколько ветвей по условиям. При объединении он ждёт только те ветви, которые действительно были активированы. Из-за этого рассуждать о нём сложнее, чем о паре exclusive/parallel.
Ошибки, retries и incidents
Нужно разделять три разных результата:
BPMN error
Предусмотренный процессом исход: отказ проверки, недоступный бизнес-вариант, отрицательный ответ. Его ловит boundary error event или error event subprocess в подходящей области.
throw new BpmnError("VALIDATION_FAILED", "Input is not valid");Техническое исключение
Сбой кода, сети, БД или инфраструктуры. Если исключение выходит из асинхронного job, job executor уменьшает число retries. После исчерпания попыток возникает incident.
Incident
Сохранённая проблема исполнения, которая требует восстановления причины и повторного запуска job. Incident нельзя считать бизнес-веткой процесса.
Retry безопасен только для повторяемой операции. Если внешний вызов мог выполниться, а ответ потерялся, повтор способен создать дубль. Нужны идемпотентный контракт, idempotency key либо проверка уже выполненного действия.
Как разбираю зависший или упавший процесс
- Нахожу process instance и версию process definition.
- Смотрю текущие activity instances, jobs и incidents.
- Определяю последнюю сохранённую точку.
- Проверяю exception stack trace и failed activity.
- Сопоставляю переменные с ожидаемым контрактом.
- Выясняю, какой код выполнялся в той же транзакции.
- Проверяю, были ли внешние необратимые эффекты.
- Только после исправления причины повторяю job или изменяю экземпляр.
Удалить incident недостаточно: failed job и причина ошибки от этого не исправляются.
Что проверяю при изменении BPMN
- не изменилась ли семантика уже запущенных экземпляров;
- совместимы ли новые переменные со старой версией процесса;
- где появились или исчезли transaction boundaries;
- не станет ли внешний вызов повторяться после retry;
- у каждого ли gateway определено поведение для пограничного входа;
- не вырос ли текст или JSON больше допустимого размера;
- охватывает ли error event нужную область;
- есть ли тест happy path, альтернативной ветки и ошибки;
- нужна ли миграция экземпляров или достаточно новой версии definition.
Вопросы для собеседования
Является ли каждый service task отдельной транзакцией?
Нет. Синхронное исполнение продолжается до wait state. Несколько элементов могут выполниться внутри одной транзакции.
Что меняет asyncBefore?
Перед activity появляется transaction boundary и job. Предыдущий участок может зафиксироваться, а activity позже выполнит job executor с собственной retry-моделью.
Чем BPMN error отличается от Java exception?
BPMN error — моделируемый результат, который можно поймать BPMN-событием. Необработанный Java exception приводит к rollback текущей команды; в job он участвует в retries и может породить incident.
Может ли parallel gateway исполнять ветви параллельно?
Он моделирует параллельные токены, но не обязательно параллельные Java threads. Реальная конкурентность зависит от async boundaries, jobs и конфигурации executor.
Почему процесс мог выполнить внешний вызов дважды?
Транзакция движка могла откатиться после успешного внешнего эффекта. Job повторился, потому что Camunda не знает, что удалённая операция завершилась.
Мои кейсы
- Кейс deadlock из-за асинхронных шагов BPMN
- Кейс контракты переменных и gateway в BPMN
- Кейс глобальный обработчик ошибок BPMN
Связанные заметки
- Транзакции принцип ACID
- Изоляция транзакций
- Работа с исключениями в Java
- deadlock
- Микросервисная архитектура