Camunda BPMN
Экспериментальный модуль
Экспериментальный модуль является полностью рабочим и протестированным, но требует дополнительной апробации и аналитики по использованию.
Поэтому API может получить незначительные изменения до полной готовности.
Модуль подключает встроенный движок Camunda 7 для выполнения BPMN-процессов внутри приложения Kora.
Он создает и настраивает ProcessEngine, связывает его с JDBC-источником данных, регистрирует исполнителей из графа приложения, загружает BPMN / FORM / DMN-ресурсы из classpath и добавляет телеметрию выполнения.
Чтобы предоставить Camunda 7 REST API и веб-приложения Cockpit / Admin / Tasklist по HTTP, используйте вместе с этим модулем отдельный модуль Camunda 7 REST.
Подключение¶
Зависимость build.gradle:
Модуль:
Зависимость build.gradle.kts:
Модуль:
Модуль требует подключения модуля JDBC.
По умолчанию используется основной DataSource приложения, но при необходимости можно предоставить отдельный DataSource с тегом @Tag(CamundaBpmn.class).
Конфигурация¶
Пример полной конфигурации, описанной в классе CamundaEngineBpmnConfig:
camunda {
engine {
bpmn {
jobExecutor {
corePoolSize = 5 //(1)!
maxPoolSize = 25 //(2)!
queueSize = 25 //(3)!
maxJobsPerAcquisition = 2 //(4)!
virtualThreadsEnabled = false //(5)!
}
deployment {
tenantId = "Camunda" //(6)!
name = "KoraEngineAutoDeployment" //(7)!
deployChangedOnly = true //(8)!
resources = ["classpath:bpm"] //(9)!
delay = "1m" //(10)!
}
parallelInitialization {
enabled = true //(11)!
validateIncompleteStatements = true //(12)!
}
admin {
id = "admin" //(13)!
password = "admin" //(14)!
firstname = "Ivan" //(15)!
lastname = "Ivanov" //(16)!
email = "admin@mail.ru" //(17)!
}
telemetry {
logging {
enabled = false //(18)!
stacktrace = true //(19)!
}
metrics {
enabled = true //(20)!
slo = [1, 10, 50, 100, 200, 500, 1000, 2000, 5000, 10000, 20000, 30000, 60000, 90000] //(21)!
tags = { //(22)!
"key1" = "value1"
"key2" = "value2"
}
}
engineTelemetryEnabled = false //(23)!
tracing {
enabled = true //(24)!
attributes = { //(25)!
"key1" = "value1"
"key2" = "value2"
}
}
}
}
}
}
- Минимальное количество постоянно живущих потоков в
JobExecutor(по умолчанию:5). - Максимальное количество потоков в
JobExecutor(по умолчанию:25). - Размер очереди задач
JobExecutor, при превышении которого новые задачи отклоняются (по умолчанию:25). - Максимальное количество задач, забираемых
JobExecutorза один запрос (по умолчанию:Runtime.getRuntime().availableProcessors() * 2). - Использовать виртуальные потоки в качестве основы
JobExecutor(по умолчанию:false). При включении этой опции настройки размера пула и очереди не используются. - Идентификатор
tenantдля загрузки ресурсов (по умолчанию не указан, опционально). - Имя загрузки ресурсов (по умолчанию:
KoraEngineAutoDeployment). - Загружать только измененные ресурсы за счет фильтрации дубликатов в
Camunda(по умолчанию:true). - Список путей для поиска
BPMN/FORM/DMN-ресурсов (обязательный, по умолчанию не указан). Поддерживаются только пути с префиксомclasspath:. - Задержка перед загрузкой ресурсов в движок (по умолчанию не указана, опционально).
- Включить параллельную инициализацию движка (по умолчанию:
true). - Проверять незавершенные выражения движка при параллельной инициализации (по умолчанию:
true). - Идентификатор администратора
Camunda(обязательный, по умолчанию не указан). Вся секцияadminявляется опциональной. - Пароль администратора
Camunda(обязательный, по умолчанию не указан). Вся секцияadminявляется опциональной. - Имя администратора
Camunda(по умолчанию не указано, опционально). Если не указано, используетсяidв верхнем регистре. - Фамилия администратора
Camunda(по умолчанию не указана, опционально). Если не указана, используетсяidв верхнем регистре. - Адрес электронной почты администратора
Camunda(по умолчанию не указан, опционально). Если не указан, используется<id>@localhost. - Включает логирование модуля (по умолчанию:
false). - Включает логирование стек-трейса ошибок (по умолчанию:
true). - Включает метрики модуля (по умолчанию:
true). - Настройка SLO для метрик (по умолчанию:
ru.tinkoff.kora.telemetry.common.TelemetryConfig.MetricsConfig#DEFAULT_SLO). - Теги метрик (по умолчанию:
{}). - Включает сбор встроенной телеметрии движка
Camunda(по умолчанию:false). - Включает трассировку модуля (по умолчанию:
true). - Атрибуты трассировки (по умолчанию:
{}).
camunda:
engine:
bpmn:
jobExecutor:
corePoolSize: 5 #(1)!
maxPoolSize: 25 #(2)!
queueSize: 25 #(3)!
maxJobsPerAcquisition: 2 #(4)!
virtualThreadsEnabled: false #(5)!
deployment:
tenantId: "Camunda" #(6)!
name: "KoraEngineAutoDeployment" #(7)!
deployChangedOnly: true #(8)!
resources: #(9)!
- "classpath:bpm"
delay: "1m" #(10)!
parallelInitialization:
enabled: true #(11)!
validateIncompleteStatements: true #(12)!
admin:
id: "admin" #(13)!
password: "admin" #(14)!
firstname: "Ivan" #(15)!
lastname: "Ivanov" #(16)!
email: "admin@mail.ru" #(17)!
telemetry:
logging:
enabled: false #(18)!
stacktrace: true #(19)!
metrics:
enabled: true #(20)!
slo: [1, 10, 50, 100, 200, 500, 1000, 2000, 5000, 10000, 20000, 30000, 60000, 90000] #(21)!
tags: #(22)!
key1: value1
key2: value2
engineTelemetryEnabled: false #(23)!
tracing:
enabled: true #(24)!
attributes: #(25)!
key1: value1
key2: value2
- Минимальное количество постоянно живущих потоков в
JobExecutor(по умолчанию:5). - Максимальное количество потоков в
JobExecutor(по умолчанию:25). - Размер очереди задач
JobExecutor, при превышении которого новые задачи отклоняются (по умолчанию:25). - Максимальное количество задач, забираемых
JobExecutorза один запрос (по умолчанию:Runtime.getRuntime().availableProcessors() * 2). - Использовать виртуальные потоки в качестве основы
JobExecutor(по умолчанию:false). При включении этой опции настройки размера пула и очереди не используются. - Идентификатор
tenantдля загрузки ресурсов (по умолчанию не указан, опционально). - Имя загрузки ресурсов (по умолчанию:
KoraEngineAutoDeployment). - Загружать только измененные ресурсы за счет фильтрации дубликатов в
Camunda(по умолчанию:true). - Список путей для поиска
BPMN/FORM/DMN-ресурсов (обязательный, по умолчанию не указан). Поддерживаются только пути с префиксомclasspath:. - Задержка перед загрузкой ресурсов в движок (по умолчанию не указана, опционально).
- Включить параллельную инициализацию движка (по умолчанию:
true). - Проверять незавершенные выражения движка при параллельной инициализации (по умолчанию:
true). - Идентификатор администратора
Camunda(обязательный, по умолчанию не указан). Вся секцияadminявляется опциональной. - Пароль администратора
Camunda(обязательный, по умолчанию не указан). Вся секцияadminявляется опциональной. - Имя администратора
Camunda(по умолчанию не указано, опционально). Если не указано, используетсяidв верхнем регистре. - Фамилия администратора
Camunda(по умолчанию не указана, опционально). Если не указана, используетсяidв верхнем регистре. - Адрес электронной почты администратора
Camunda(по умолчанию не указан, опционально). Если не указан, используется<id>@localhost. - Включает логирование модуля (по умолчанию:
false). - Включает логирование стек-трейса ошибок (по умолчанию:
true). - Включает метрики модуля (по умолчанию:
true). - Настройка SLO для метрик (по умолчанию:
ru.tinkoff.kora.telemetry.common.TelemetryConfig.MetricsConfig#DEFAULT_SLO). - Теги метрик (по умолчанию:
{}). - Включает сбор встроенной телеметрии движка
Camunda(по умолчанию:false). - Включает трассировку модуля (по умолчанию:
true). - Атрибуты трассировки (по умолчанию:
{}).
Секция deployment является опциональной: если она не указана, модуль не выполняет автоматическую загрузку ресурсов.
Если секция указана, resources должна содержать хотя бы один путь.
Ресурсы ищутся рекурсивно в classpath; неподдерживаемые пути без префикса classpath: пропускаются.
Метрики модуля описаны в разделе Справочник метрик.
Загрузка ресурсов¶
Когда секция deployment присутствует, модуль автоматически загружает ресурсы процессов в движок после его создания.
Ресурсы размещаются в classpath (обычно в каталоге src/main/resources) и указываются в списке resources:
- Когда секция
deploymentприсутствует, требуется хотя бы один путь. Поддерживаются только пути с префиксомclasspath:.
При следующей структуре каталогов путь classpath:bpm сканируется рекурсивно, и каждый поддерживаемый ресурс внутри него загружается:
Правила загрузки, которые стоит учитывать:
- Поддерживаемые типы ресурсов — модели процессов
BPMN, формыFORMи таблицы решенийDMN. - Загружаются только пути с префиксом
classpath:. Любой другой путь пропускается с предупреждением в логе. - Пути сканируются рекурсивно, поэтому вложенные каталоги внутри указанного пути также включаются.
- При
deployChangedOnly = true(по умолчанию) включается фильтрация дубликатовCamunda, поэтому повторно загружаются только ресурсы, изменившиеся с момента предыдущей загрузки. - Опциональный
tenantIdпривязывает загрузку к конкретномуtenant, аdelayоткладывает загрузку на заданное время после старта. - Загрузка регистрируется под именем
name(по умолчаниюKoraEngineAutoDeployment). - Если вся секция
deploymentопущена, модуль не загружает никакие ресурсы — предполагается, что вы загружаете их самостоятельно черезRepositoryService.
Исполнители¶
Camunda может вызывать компоненты приложения в качестве исполнителей процесса.
Обычные экземпляры JavaDelegate регистрируются в контексте по полному имени класса (canonicalName) и по короткому имени класса (simpleName).
Внутри execute(...) вы читаете и записываете переменные процесса через DelegateExecution:
@Component
public final class ScoreCustomerDelegate implements JavaDelegate {
private static final Logger logger = LoggerFactory.getLogger(ScoreCustomerDelegate.class);
@Override
public void execute(DelegateExecution execution) {
int scoring = ThreadLocalRandom.current().nextInt(1, 100);
logger.info("Scored {} with result {}.", execution.getBusinessKey(), scoring);
execution.setVariable("result", scoring);
}
}
@Component
class ScoreCustomerDelegate : JavaDelegate {
private val logger = LoggerFactory.getLogger(ScoreCustomerDelegate::class.java)
override fun execute(execution: DelegateExecution) {
val scoring = ThreadLocalRandom.current().nextInt(1, 100)
logger.info("Scored {} with result {}.", execution.businessKey, scoring)
execution.setVariable("result", scoring)
}
}
Поскольку JavaDelegate регистрируется по короткому имени класса, serviceTask в модели BPMN ссылается на него по simpleName через camunda:delegateExpression:
<bpmn:serviceTask id="Activity_0tusr5p" name="Score Customer"
camunda:delegateExpression="${ScoreCustomerDelegate}">
<bpmn:incoming>Flow_score_in</bpmn:incoming>
<bpmn:outgoing>Flow_score_out</bpmn:outgoing>
</bpmn:serviceTask>
Используйте KoraDelegate для произвольного имени исполнителя.
Метод key() по умолчанию возвращает canonicalName, но его можно переопределить, чтобы задать имя, используемое в выражениях BPMN:
На объявленный таким образом исполнитель ссылаются как ${myKey} в camunda:delegateExpression, поэтому имя, используемое в модели процесса, больше не зависит от имени класса.
Каждый исполнитель перед вызовом оборачивается фабрикой KoraDelegateWrapperFactory: она ответвляет текущий Context Kora на время выполнения исполнителя и применяет телеметрию модуля вокруг execute(...).
Вы можете предоставить собственную KoraDelegateWrapperFactory в виде @Component, чтобы изменить это поведение.
Сервисы движка¶
Модуль предоставляет стандартные сервисы Camunda в виде компонентов графа зависимостей:
RuntimeServiceRepositoryServiceManagementServiceAuthorizationServiceDecisionServiceExternalTaskServiceFilterServiceFormServiceTaskServiceHistoryServiceIdentityService
Эти сервисы можно внедрять в ваши компоненты обычным образом.
Запуск процессов и взаимодействие с ними¶
Внедрите ProcessEngine (или любой из перечисленных выше сервисов движка) в свои компоненты, чтобы запускать процессы и управлять их экземплярами.
Процесс запускается по id процесса BPMN через RuntimeService, а определения процессов можно запрашивать через RepositoryService:
@Component
@HttpController("/camunda")
public final class CamundaController {
private final ProcessEngine processEngine;
public CamundaController(ProcessEngine processEngine) {
this.processEngine = processEngine;
}
@HttpRoute(method = HttpMethod.GET, path = "/start/onboarding")
public String startOnboarding() {
String businessKey = UUID.randomUUID().toString();
ProcessInstance instance = processEngine.getRuntimeService()
.startProcessInstanceByKey("Onboarding", businessKey);
return instance.getId();
}
}
@Component
@HttpController("/camunda")
class CamundaController(private val processEngine: ProcessEngine) {
@HttpRoute(method = HttpMethod.GET, path = "/start/onboarding")
fun startOnboarding(): String {
val businessKey = UUID.randomUUID().toString()
val instance = processEngine.runtimeService
.startProcessInstanceByKey("Onboarding", businessKey)
return instance.id
}
}
Запущенный процесс можно продвигать и извне движка: RuntimeService.correlateMessage(...) доставляет событие-сообщение BPMN, а TaskService / FormService завершают пользовательские задачи и отправляют формы:
@Component
@HttpController("/camunda/process/onboarding")
public final class OnboardingController {
private final FormService formService;
private final TaskService taskService;
private final RuntimeService runtimeService;
public OnboardingController(FormService formService, TaskService taskService, RuntimeService runtimeService) {
this.formService = formService;
this.taskService = taskService;
this.runtimeService = runtimeService;
}
@HttpRoute(path = "/cancel/{businessKey}", method = HttpMethod.GET)
public String customerCancellation(@Path String businessKey) {
runtimeService.correlateMessage("MessageCustomerCancellation", businessKey);
return "Cancelled: " + businessKey;
}
@HttpRoute(path = "/order/{businessKey}", method = HttpMethod.GET)
public String customerOrder(@Path String businessKey) {
Task task = taskService.createTaskQuery().processInstanceBusinessKey(businessKey).active().singleResult();
formService.submitTaskForm(task.getId(), Map.of("approved", true));
return "Approved: " + businessKey;
}
}
@Component
@HttpController("/camunda/process/onboarding")
class OnboardingController(
private val formService: FormService,
private val taskService: TaskService,
private val runtimeService: RuntimeService
) {
@HttpRoute(path = "/cancel/{businessKey}", method = HttpMethod.GET)
fun customerCancellation(@Path businessKey: String): String {
runtimeService.correlateMessage("MessageCustomerCancellation", businessKey)
return "Cancelled: $businessKey"
}
@HttpRoute(path = "/order/{businessKey}", method = HttpMethod.GET)
fun customerOrder(@Path businessKey: String): String {
val task = taskService.createTaskQuery().processInstanceBusinessKey(businessKey).active().singleResult()
formService.submitTaskForm(task.id, mapOf("approved" to true))
return "Approved: $businessKey"
}
}
Источник данных и транзакции¶
Движок сохраняет свое состояние через JDBC-DataSource, поэтому модуль JDBC обязателен.
По умолчанию модуль переиспользует основной DataSource приложения, предоставляемый движку под тегом @Tag(CamundaBpmn.class).
Чтобы выделить движку отдельный источник данных, предоставьте собственный DataSource с этим тегом:
Компонент CamundaEngineDataSource абстрагирует DataSource движка вместе с его CamundaTransactionManager.
Реализация по умолчанию выполняет JDBC через DataSource с тегом @Tag(CamundaBpmn.class); вы можете переопределить CamundaEngineDataSource как @Component, чтобы полностью контролировать, как движок получает соединения и управляет транзакциями.
Исполнитель, выполняющий собственную JDBC-работу, может проводить ее внутри транзакции движка через CamundaTransactionManager.
inContinueTx(...) переиспользует соединение текущей транзакции движка (открывая новую только если активной нет), тогда как inNewTx(...) всегда открывает новую транзакцию; currentConnection() возвращает дескриптор для commit() / rollback() текущей транзакции:
@Component
public final class AuditDelegate implements JavaDelegate {
private final CamundaTransactionManager transactionManager;
public AuditDelegate(CamundaTransactionManager transactionManager) {
this.transactionManager = transactionManager;
}
@Override
public void execute(DelegateExecution execution) {
transactionManager.inContinueTx(() -> {
// JDBC work sharing the engine transaction
});
}
}
Исполнитель задач и готовность¶
Движок выполняет асинхронные продолжения и таймеры через JobExecutor.
Реализация выбирается опцией jobExecutor.virtualThreadsEnabled: при false (по умолчанию) используется исполнитель на пуле потоков с размерами, задаваемыми corePoolSize / maxPoolSize / queueSize / maxJobsPerAcquisition; при true используется исполнитель на виртуальных потоках, а размеры пула и очереди игнорируются (см. пояснения в разделе Конфигурация).
Модуль автоматически регистрирует пробу готовности, которая сообщает о приложении как UP только после того, как JobExecutor становится активным.
Пока исполнитель задач не активирован, проба падает с сообщением Camunda BPMN Engine JobExecutor is not active, что удерживает приложение вне ротации, пока движок еще запускается.
Пользователь-администратор и Cockpit¶
Когда секция admin присутствует, модуль создает пользователя-администратора Camunda, гарантирует существование группы camunda-admin с полными правами и добавляет в нее пользователя (см. пояснения admin в разделе Конфигурация).
Именно эту учетную запись вы используете для входа в веб-приложения Cockpit / Admin / Tasklist, предоставляемые модулем Camunda 7 REST.
Если секция admin опущена, пользователь не создается.
Конфигурация движка¶
Для дополнительной настройки зарегистрируйте компонент ProcessEngineConfigurator.
Метод prepare(...) вызывается до создания ProcessEngine и получает ProcessEngineConfiguration; метод setup(...) вызывается после создания движка:
Плагины¶
Вы можете зарегистрировать произвольные ProcessEnginePlugin, предоставив их в качестве компонентов в контейнере зависимостей Kora.
Модуль собирает все такие компоненты и передает их в конфигурацию движка при создании ProcessEngine:
@Component
public final class SimpleProcessEnginePlugin implements ProcessEnginePlugin {
@Override
public void preInit(ProcessEngineConfigurationImpl configuration) {
}
@Override
public void postInit(ProcessEngineConfigurationImpl configuration) {
}
@Override
public void postProcessEngineBuild(ProcessEngine engine) {
}
}
Версия Camunda¶
Определенная версия Camunda доступна как внедряемый компонент CamundaVersion.
Его version() возвращает строку версии, сообщаемую пакетом Camunda, а isEnterprise() возвращает true, когда в classpath присутствует enterprise-дистрибутив (-ee):
Телеметрия¶
Модуль сообщает собственные логирование, метрики и трассировку для выполнения исполнителей через секцию конфигурации telemetry.
Метрики описаны в разделе Справочник метрик, а ответвление Context, выполняемое KoraDelegateWrapperFactory, ограничивает эту телеметрию рамками каждого вызова исполнителя.
Независимо от телеметрии модуля, telemetry.engineTelemetryEnabled переключает сбор собственной встроенной телеметрии Camunda (по умолчанию отключен).