Привет! Я Вероника из Clevertec, занимаюсь бэкендом на банковском проекте. Этот текст написан из желания помочь разработчикам, которым только предстоит познакомиться с Camunda. Что это, для чего, как работает и почему восьмая версия совсем не похожа на предыдущую? Делюсь своим опытом, добытым путём ошибок.
Как я не подружилась с Camunda на старте
Сейчас я работаю на проекте, где Camunda активно используют. Перед стартом проекта о платформе я ничего не слышала, поэтому посмотрела видео про Camunda 7 и, посчитав свои знания исчерпывающими, радостно окунулась в разработку. Каково же было моё удивление, когда оказалось, что Camunda 7 не равно Camunda 8.
В чем главное различие? 8 версия полностью основана на Zeebe. Это абсолютно другая архитектура, в ней нет PostgreSQL и нет зависимости от реляционной базы данных. Также 8 версия разворачивается отдельно, а не встраивается зависимостью в SpringBoot. Кроме того, в новой версии исключили значительное количество элементов. Но они потихоньку возвращаются.
Со всем этим мне пришлось разобраться и подружиться. А потом захотелось поделиться здесь инструкцией для тех, кто ещё на старте этого пути. Я пишу на java, поэтому и примеры у меня будут на java.
Идём в основы и разбираемся с BPMN
Для начала я вам рекомендую подружиться с сайтом официальной документации. Не могу сказать, что он юзер-френдли, но пользоваться им придется часто.
Хотя в документации настоятельно советует пользоваться Kubernetes с Minikube или KIND для разработки на локальной машине, я предпочитаю пользоваться Docker. Делюсь ссылкой на гит. В корне проекта есть докер-компоуз, чтобы вы смогли локально развернуть у себя Camunda.
Что такое Camunda вообще? Camunda Platform 8 управляет сложными бизнес-процессами, которые охватывают людей, системы и устройства. С помощью Camunda бизнес-пользователи сотрудничают с разработчиками для моделирования и автоматизации сквозных процессов с использованием блок-схем на основе BPMN, а также таблиц решений DMN, которые способствуют скорости, масштабированию и логике принятия решений. Замечу, что Camunda 8 позиционируется себя как универсальный оркестратор процессов. Если простым языком, то это BPMN-диаграмма, положенная на ваш java-код.
Немного подробностей о BPMN-диаграмме. Business Process Model and Notation (нотация моделирования бизнес-процессов) – это совокупность блок-схем, с помощью которых отображаются бизнес-процессы. BPMN-диаграмма показывает, в какой последовательности совершаются рабочие действия и перемещаются потоки информации. BPMN-диаграмма облегчает жизнь всем членам команды, наглядно и просто отражая процессы, которые происходят в вашем проекте.
Мой совет – участвовать в разработке процессов BPMN совместно с аналитиком. Если этого не делать, то может получиться так:
Переходим к вечеру пятницы. Ставим задачу
Для понимания, как переложить BPMN-диаграмму на java-код, давайте отрисуем небольшой процесс. Для этого вам понадобится Camunda Modeler, который можно скачать здесь.
Постановка задачи: наступила пятница, необходимо принять решение, как провести вечер.
Каждый процесс должен иметь начало и конец. Как видите, в нашем процессе они также присутствуют. На проекте мы развернули отдельный микросервис, в котором происходит деплой bpmn-диаграмм. Для этого используется зависимость spring-zeebe-starter. Обратите внимание, что сейчас уже актуальна версия выше.
<dependency>
<groupId>io.camunda</groupId>
<artifactId>spring-zeebe-starter</artifactId>
<version>8.0.7</version>
</dependency>
Также необходимо поместить аннотацию @EnableZeebeClient в Spring Boot Application. Кроме того, я указываю дополнительно аннотацию @ZeebeDeployment(resources = { "classpath*:/process/*.bpmn"}), в которой прописываю, где именно лежат диаграммы.
В application.yml следует сконфигурировать подключение к Zeebe broker. Для локальной разработки с помощью Docker это будет иметь вид:
zeebe.client:
broker.gateway-address: localhost:26500
zeebe:
client:
security:
plaintext: true
Но процесс необходимо стартовать. Вы можете достучаться до микросервиса посредством REST-запроса, передав необходимые параметры в теле запроса.
@RequiredArgsConstructor
@RestController
@RequestMapping("/start")
public class StartController {
private final StartService startService;
@PostMapping()
@SneakyThrows
public ProcessData startProcess(@RequestBody ProcessVariables request) {
return startService.startProcess(request);
}
}
@Data
public class ProcessVariables {
/**
* Количество денег в начале вечера пятницы
*/
@NotNull
@Min(value = 50, message = "sumOfMoney should not be less than 50")
@Max(value = 150, message = "sumOfMoney should not be greater than 150")
private Integer sumOfMoney;
}
Далее мы создаем новый инстанс процесса и передаем в него переменные, которые нам понадобятся для процесса. Что важно: bpmnProcessId соответствует Id процесса, которое указано на возможной диаграмме пятничного вечера. В variables я показываю, что в них можно добавить не только те, которые пришли из запроса, но и дополнить необходимыми. Например, вы сходите в базу данных и дополните uuid.
@Service
@RequiredArgsConstructor
@Slf4j
public class StartService {
private static final String PROCESS_NAME = "FRIDAY_EVENING_PROCESS";
private final ZeebeClient zeebe;
public ProcessData startProcess(ProcessVariables request) {
String uuid = UUID.randomUUID().toString();
ProcessInstanceEvent instanceEvent = zeebe.newCreateInstanceCommand()
.bpmnProcessId(PROCESS_NAME)
.latestVersion()
.variables(Map.of("sumOfMoney", request.getSumOfMoney(),
"messageId", uuid))
.send().join();
return new ProcessData()
.setProcessInstanceKey(instanceEvent.getProcessInstanceKey());
}
}
Разберём типы задач
Camunda 8 реализует различные типы задач, но в основном вы будете пользоваться Сервисными задачами.
Нужно сопоставить ZeebeWorker, который будет реализовывать сервисную задачу.
@Component
@Slf4j
public class DecideHowSpendFridayNightTask {
@ZeebeWorker(type = "decideHowSpendFridayNight", autoComplete = true)
public ProcessVariables decideHowSpendFridayNight(ActivatedJob job) {
log.info("works worker decideHowSpendFridayNight");
ProcessVariables variables = job.getVariablesAsType(ProcessVariables.class);
variables.setSumOfMoney(variables.getSumOfMoney() + 10);
log.info("sumOfMoney after increase is {}", variables.getSumOfMoney());
return variables;
}
}
Как можно увидеть, типы совпадают, поэтому когда токен дойдет до задачи с типом “decideHowSpendFridayNight”, начнется выполнение кода. Обратите внимание, если вы используете autoComplete = true, нет необходимости завершать обработку задачи самостоятельно, интеграция с Spring сделает это за вас.
При помощи job.getVariablesAsType(), вы можете получить свой собственный класс, в котором сопоставляются переменные процесса.
Пара слов о шлюзах
В процессах удобно использовать различные шлюзы. Здесь я для понимания привела Эксклюзивные Шлюзы и Параллельный Шлюз.
Эксклюзивные Шлюзы (Условия) включаются в состав бизнес-процесса для разделения потока операций на несколько альтернативных маршрутов. Для нашего экземпляра процесса может быть выбран лишь один из предложенных маршрутов.
Параллельные шлюзы необходимы для объединения и создания параллельных маршрутов.
В нашем примере в зависимости от количества денег вы либо останетесь дома, либо пойдете в клуб. Причем, чтобы вы ни выбрали, параллельно с этим вы будете общаться с друзьями в мессенджере. Обратите внимание: к задаче “пойти в клуб” присоединено событие типа “ошибка” (error event).
Error в обязательном порядке обладает наименованием и кодом. При наступлении определенной ситуации, можно выбросить ZeebeBpmnError – и процесс пойдет по пути экстренного отъезда домой.
@Component
@Slf4j
public class GoToClubTask {
@ZeebeWorker(type = "goToClub", autoComplete = true)
public Contact goToClub() {
log.info("works worker goToClub");
//рандомно определяем количество выпитых шотов
int numberOfShots = (int) (Math.random() * 15);
if (numberOfShots > 8) {
throw new ZeebeBpmnError(
"TOO_MUCH_ALCOHOL", "you drank shots= " + numberOfShots);
} else {
//уходим с контактом HR о работе
return new Contact().setTgContact("@hr_contact");
}
}
}
Если шотов было выпито более чем 8, выбрасывается ZeebeBpmnError, errorCode который совпадает с тем, который указан в диаграмме.
Хочу отметить, что можно порождать новые переменные процесса, не только те, которые указаны на старте. Например в воркере с типом “goToClub”, который указан выше, при удачном стечении обстоятельств можно получить контакт HR, и tgContact станет переменной процесса, которую можно отловить в другой сервисной задаче.
В сервисной задаче “Проснуться утром в субботу отдохнувшим и бодрым” я хочу отловить все переменные, которые существуют в процессе
@Component
@Slf4j
public class WakeUpOnSaturdayMorningTask {
@ZeebeWorker(type = "wakeUpOnSaturdayMorning", autoComplete = true,
forceFetchAllVariables = true)
public void wakeUpOnSaturdayMorning(ActivatedJob job) {
log.info("works worker wakeUpOnSaturdayMorning");
AllVariables allVariables = job.getVariablesAsType(AllVariables.class);
log.info("messageId {}, tgContact {}, sumOfMoney {}",
allVariables.getMessageId(), allVariables.getTgContact(),
allVariables.getSumOfMoney());
}
}
Для этого мне необходимо прописать флаг forceFetchAllVariables=true – и все переменные процесса станут вам доступны. Как можно видеть, я собрала их в отдельный класс AllVariables.
Закончу описанием сообщения
В сервисной задаче “Периодически общаться с друзьями”, мы отправляем сообщение, которое отлавливаем в процессе и тем самым стартуем задачу “Принять приглашение встретиться с другом”.
Наше сообщение обведено пунктирным кругом. Это значит, что сообщение не прерывающееся. То есть, основной процесс не остановится, вы и примете приглашение, и продолжите находиться либо в клубе, либо дома с пиццей.
Посмотрим внимательнее, как отправить сообщение.
@Component
@RequiredArgsConstructor
@Slf4j
public class ChatWithFriendsPeriodicallyTask {
private final ZeebeClient zeebe;
@ZeebeWorker(type = "chatWithFriendsPeriodically", autoComplete = true)
public void chatWithFriendsPeriodically(@ZeebeVariable String messageId) {
log.info("works worker chatWithFriendsPeriodically");
PublishMessageResponse message = zeebe.newPublishMessageCommand()
.messageName("MEET_MESSAGE")
.correlationKey(messageId)
.send()
.join();
log.info("There were sent message with messageKey {} and correlationKey {}",
message.getMessageKey(), messageId);
}
}
Для этого в сервисной задаче необходимо опубликовать сообщение, передав туда наименование сообщения и ключ корреляции. Как видно, в нашем случае ключом корреляции выступает messageId. Наименование сообщения плюс его ключ корреляции составляют уникальность сообщения. Вы будете уверены, что такое сообщение вызовется только один раз.
Естественно, наименование сообщения и его ключ должны быть отражены не только в коде, но и на BPNM-диаграмме.
Запустим процесс и отследим по логам, что происходит
Когда вы стартовали докер компоуз, на порту 8081 развернулся operate - ui, позволяющий мониторить и анализировать процессы. То есть в нем вы увидите диаграмму, которая была задеплоена. Также operate позволяет в реальном времени отслеживать, на каком шаге находится экземпляр процесса.
Мы видим, что процесс пошел по развилке “пойти в клуб”, ошибки выброшено не было, и в то же время мы успешно пообщались с друзьями и приняли приглашение на встречу.
В сервисных задачах я не писала логику, но на самом деле в них можно делать что угодно: ходить в базу данных, стучаться в сторонние микросервисы, получать сообщения из Kafka.
Как я использую Camunda в реальности?
Отойдём от метафорических примеров и пятничного вечера. Я работаю на банковском проекте, и там эту технологию используют очень широко. Например, мы получаем онлайн-согласия клиента, получаем данные из ЕСИА (Единая система идентификации и аутентификации), обновляем документы клиента и передаем запрашиваемые данные на госуслуги. В нашем случае это удобно и для бизнеса, и для разработчиков.
Нужна ли Camunda всем? Однозначно нет.
Давайте обсудим ваш опыт использования в комментариях.
Комментарии (20)
TldrWiki
21.06.2024 21:51Спасибо за статью. Camunda редкостная гадость потому что параллельно программной логике вносит дополнительный путь исполнения что затрудняет разработку и отладку. Уйдет в прошлое как gwt.
ververesk Автор
21.06.2024 21:51Спасибо, что было интересно. Не соглашусь, что Camunda редкостная гадость, если честно, я была такого же мнения, но после многочисленных шагов принятия, могу сказать, что в определенных моментах она прямо хорошо ложится на процесс
vdshat
21.06.2024 21:51Любой BPM фреймворк нацелен на то, чтобы ввести уровень абстракции для дальнейшего переиспользования больших блоков процесса и эффективного развития процессов. Даже если вы работаете на уровне небольших приложений есть выгода от использования BPM. Например, визарды, заполение форм можно сделать с помощью BPM или workflow.
В случае встроенного BPM отладка такая же как в обычном приложении. Если BPM серверный, то либо удаленная отладка, либо как в любом распределенном приложении. Опять же хороший тон - присать тесты на процесс перед заливкой и проблем будет значительно меньше.
TldrWiki
21.06.2024 21:51+1Вы данные храните в единой базе данных или они у вас размазаны по икселькам, json на удаленной стороне и в очереди сообщений? Почему тогда путь выполнения вы разделяете на программный и камундный?
Про переиспользование, а что камунда сама по себе работает, без кода со стороны приложения?
ververesk Автор
21.06.2024 21:51Смотрите, в камунде есть процесс variables, ограничение по ним вроде 4 мегабайта, но могу ошибаться. Так что в этих переменных мы храним uuid и другие технические данные. Поясню, если мне надо достать какую то информацию из базы данных и что то с ней сделать, то я получаю uuid из process variables, и у же по нему достаю нужные мне информацию. То есть вся основная информация хранится в базе данных.
TldrWiki
21.06.2024 21:51Во-первых в Камунде можно хранить любые данные. Во-вторых я не про хранение данных, а про разделение потока выполнения. Я применил принцип single source of truth к потоку выполнения приложения, который при внедрении камунды раздваивается.
ververesk Автор
21.06.2024 21:51не слышала честно говоря про этот принцип, однозначно почитаю, спасибо
ayrtonSK
21.06.2024 21:51Чтобы хранить в камунде данные, надо чтобы они там как то оказались из более надёжного источника данных, вы периодически синхронизируете кучу данных из другой бд?
ayrtonSK
21.06.2024 21:51Отладка в камунде это боль, так как контекстом то она не рулит, а рулят микросервисы и действительно это превращается в туже боль отладки микросервисов, особенно если кто то решил намельчить и использовал один паттерн микросервис- одна таблица. А по хорошему то надо, чтобы вся бизнес логика жила в одном sub домене core домена, тогда и проблемы не будет.
Тесты писать безусловно надо, поделитесь как на камунду вы тесты пишите?
ggo
21.06.2024 21:51Camunda 8-й версии научилась "из коробки" не выпадать в осадок, если вызываемые синхронные сервисы замедлились или таймаутят?
ayrtonSK
21.06.2024 21:51Ценность Camunda для меня сомнительная, ещё в 2013ом мы послали в урну Activity, мама камунды, ибо все это выглядело не используемо. Тогда мы сделали свой движок, который обладал контектом, а Bmpn движки обладают недоконтекстом, а набором переменных, которые надо как то туда собрать, в итоге более менее серьёзный процесс выпадает в какой-то плохо сопровождаемый проект, где сложно понять почему процесс оказался тут... ( кто поставил значения переменным, на основе которых принималось решение).
Сейчас используем Camunda, для банков это плюс, они типа её знают и поэтому заносят в плюсы.
Что по факту:
Ни какой гибкости камунды нет, любое изменение, потребует знания не только сложной Camunda, но и java, python... Где реализуется ServiceTask. Да, визуально выглядит просто, но на деле, помимо аналитика, это всегда требует разработчика, например в приведеном примере надо до клуба сбегать за пивком..
Это поражает другую проблему - у вас знания, как эта штука работает, разделяется на 2 системы и 2 головы и возникает необходимость синхронизация этого.
Отладка, это боль и страдания, когда ты не можешь вначале понять где проблема, отдебажить все это, а пытаешься понять, как такой контекст переменных образовался.
Ещё +1 система отказа и головной боли, для админов
Какие мысли по этому, лучше все написать на одной системе или на java/... или на Camunda, звучит странно, но большую часть проблем тогда удастся решить.
Но в Camunda нельзя видеть историю и её nocode движок очень слабо развит, т. е технически задачу не решить без code.
На java/... , не будет визуальности диаграммы и типа лёгкости внесения изменений,что "типа" надо архитектору/аналитику банка.
Кто как решает эту проблему?
Кто и как управляет контекстом Camunda? (устанавливаете все переменные непосредственно перед принятием решения? Или где придётся) или может как то по другому?
Идеального решения у меня нет, есть видение, кнопок возможно придётся реальзовать
Horocek
Я не понял для чего нужна комунда
ververesk Автор
мы как-то внутри команды собирали встречу, дабы решить, так в каких моментах таки использовать камунду, если кратко, то.
много взаимодействий между микросервисами
много шагов
нет фронта
Это очень краткая выжимка, возможно, напишу статью об этом