Camunda BPM для разработчика
Добро пожаловать в проект "Camunda BPM для разработчика"! Наш проект - это материалы с воркшопов и вебинаров, посвященные разработке Java-приложений с использованием процессного движка Camunda BPM.
По результатам каждого занятия публикуется полная запись (для участников), сокращенная запись (для размещения на Youtube) и исходный код проекта на GitHub.
Что необходимо иметь участнику workshop'а или разработчику, желающему самостоятельно повторить занятие?
- Компьютер с правами администратора 🖥
- Java JDK
- Camunda Modeler последней версии: https://camunda.com/download/modeler/
- Java IDE: https://www.jetbrains.com/idea/download/
- Пиво (или любой другой бодрящий напиток, опционально) 🍺
Занятие 1: Печенеги vs Половцы

Цели - научиться:
- Настраивать проект Camunda при помощи Camunda Boot Starter
- Создавать и разворачивать процесс
- Создавать Java код и связывать его с Camunda
- Работать с данными в процессе
- Обрабатывать события BPMN
Шаг 1: Сгенерировать проект Camunda с использованием Camunda BPM Initialzr
Необходимо открыть страницу по ссылке: https://start.camunda.com. Задать настройки проекта и нажать Generate Project.

Полученный архив с именем приложения распаковать и открыть в IDE. Например в IDEA - Import project - as Maven project. После открытия проекта - дождаться, пока Maven скачает зависимости. После, запустить приложение:
src/main/java/имя_приложения/Application.java > нажать правой кнопкой > >

После запуска приложения открыть браузер и перейти по ссылке: http://localhost:8080/.
Войти в веб-приложение Camunda, используя учетную запись:
Имя пользователя: demo Пароль: demo
При необходимости, используемые логин и пароль можно заменить в файле resources/application.yaml.
Полный перечень настроек application.yaml доступен по ссылке:
https://docs.camunda.org/manual/latest/user-guide/spring-boot-integration/configuration/#camunda-engine-properties
💪 Поздравляем! Вы развернули процессное приложение Camunda!
Шаг 2: Внести изменения в описание процесса и реализовать Java Delegate
Спроектируем процесс под названием "Половцы vs Печенеги" в котором смоделируем битву. Для начала создадим новый delegate класс под названием PrepareToBattle. Взаимодействие процессного движка Camunda с кодом осуществляется посредством делегатного когда, другими словами наш класс должен имплементировать интерфейс JavaDelegate. Для корректной имплементации необходимо реализовать метод execute, кроме этого к классу добавим аннотацию Component. После чего можем наполнять процесс данными - количество вражеских воинов и статус битвы. Чтобы присвоить эти переменные переменным процесса используем метод setVariable():
package com.reunico.demo; import org.camunda.bpm.engine.delegate.DelegateExecution; import org.camunda.bpm.engine.delegate.JavaDelegate; import org.springframework.stereotype.Component; @Component public class PrepareToBattle implements JavaDelegate { @Override public void execute(DelegateExecution delegateExecution) throws Exception { int enemyWarriors = (int) (Math.random() * 100); String battleStatus = "Undefined"; delegateExecution.setVariable("enemyWarriors", enemyWarriors); delegateExecution.setVariable("battleStatus", battleStatus); } }
Теперь немного поменяем наш процесс. В первую очередь поменяем его имя, для этого нажимаем на пустое пространство и видим id процесса - app-process, который выступает в роли ключа процесса и name - "Печенеги vs Половцы". Задача "Perform battle!" - это сервисная задача, которая наполнит процесс данными. Из этой задачи вызовем созданный нами класс. Нажимаем два раза на сервисный таск, после чего в поле Implementation выбираем Delegate Expression. В качестве Delegate Expression записываем название созданного класса (spring bean), но с маленькой буквой с использованием синтаксиса JUEL. После этого можем перезапустить приложение и убедиться в работоспособности процесса в Camunda Task List.


Усложним процесс, добавив в него больше вариативности. Добавим возможность ввести количество наших воинов, которых мы отправим в бой, модифицируем PrepareToBattle. Чтобы получить переменную из процесса достаточно использовать метод getVariable("warriors"). Также добавим условие победы или поражения битвы. Не забываем поместить обратно в процесс переменную, содержащую эту информацию:
package com.reunico.demo; import org.camunda.bpm.engine.delegate.DelegateExecution; import org.camunda.bpm.engine.delegate.JavaDelegate; import org.springframework.stereotype.Component; @Component public class PrepareToBattle implements JavaDelegate { @Override public void execute(DelegateExecution delegateExecution) throws Exception { int warriors = (int) delegateExecution.getVariable("warriors"); int enemyWarriors = (int) (Math.random() * 100); String battleStatus = "Undefined"; boolean isWin = false; if ((warriors - enemyWarriors) > 0) { isWin = true; battleStatus = "Victory!"; } else { battleStatus = "Fail :("; } delegateExecution.setVariable("enemyWarriors", enemyWarriors); delegateExecution.setVariable("battleStatus", battleStatus); delegateExectution.setVariable("isWin", isWin); } }
Что касается самого процесса - удалим пользовательскую задачу и добавим шлюз. Чтобы определить по какому маршруту пойдет процесс необходимо кликнуть на стрелочку, соединяющую шлюз и пользовательскую задачу, и в Condition Type выбрать пункт Expression. В самом поле Expression в зависимости от пути написать ${isWin} - для маршрута "Celebrate victory" и ${!isWin} - для поражения.

После чего можем протестировать процесс. Обратите внимание, что если мы при старте процесса не зададим переменную warriors - количество наших воинов, то выработается exeption "The process could not be started. : Unsuccessful HTTP response". По логам видно, что это null pointer exection.

Шаг 3: Добавить в описание процесса boundary error event и шлюз
Теперь добавим обработку событий в наш процесс. Для этого на сервисную задачу прикрепим Error Boundary Event, заполним его параметры. Добавим пользовательскую задачу, которая будет вызвана при срабатывании ошибки, чтобы заставить пользователя ввести число воинов:

Чтобы обработать ошибку в код делегата допишем условие:
package com.reunico.demo; import org.camunda.bpm.engine.delegate.DelegateExecution; import org.camunda.bpm.engine.delegate.BpmnError; import org.camunda.bpm.engine.delegate.JavaDelegate; import org.springframework.stereotype.Component; @Component public class PrepareToBattle implements JavaDelegate { @Override public void execute(DelegateExecution delegateExecution) throws Exception { int warriors = (int) delegateExecution.getVariable("warriors"); int enemyWarriors = (int) (Math.random() * 100); String battleStatus = "Undefined"; boolean isWin = false; if ((warriors - enemyWarriors) > 0) { isWin = true; battleStatus = "Victory!"; } else { battleStatus = "Fail :("; } if (warriors < 1 || warriors > 100) { throw new BpmnError("warriorsError"); } delegateExecution.setVariable("enemyWarriors", enemyWarriors); delegateExecution.setVariable("battleStatus", battleStatus); delegateExectution.setVariable("isWin", isWin); } }Исходный код проекта на GitHub: https://github.com/mstislavm/camundaBattle.
Полное описание в видеоматериале на YouTube:
Все материалы серии:
Camunda для разработчика: Часть 1Camunda для разработчика: Часть 2
Camunda для разработчика: Часть 3