Вы начали свой первый проект, используя автоматизацию бизнес-процессов как сервис с Camunda Cloud? Одной из первых задач будет набросать базовую архитектуру вашего решения. Этот блог-пост поможет вам ответить на важные начальные вопросы: как подключить движок выполнения процессов Zeebe к вашему приложению или к внешним системам? Что такое job worker, какую роль он играет и сколько их вообще нужно?
Подключение движка процессов к вашему приложению
Движок процессов Zeebe — это удалённая система для вашего приложения, аналогично базе данных. Приложение подключается к Zeebe по протоколу — gRPC, который, как правило, скрыт от вас (аналогично драйверам ODBC или JDBC).
С Camunda Cloud и движком Zeebe есть два основных способа подключения:
Вы пишете программный код с использованием клиентской библиотеки под нужный язык.
Используете готовый коннектор или «мост» (о терминологии позже) — требуется лишь конфигурация.
Как и всегда, у обоих подходов есть свои плюсы и минусы. Разберём их подробнее.
Связующий код (glue code)
Чтобы подключиться к Zeebe, вы внедряете клиентскую библиотеку Zeebe в своё приложение (или микросервис). Если у вас несколько приложений, каждому понадобится библиотека. Если библиотека под ваш язык отсутствует, вы можете сгенерировать gRPC-клиента самостоятельно.

С помощью клиента ваше приложение может:
Вызывать Zeebe — запускать процессы, отправлять сообщения, деплоить схемы.
Подписываться на задачи, созданные движком в контексте BPMN service tasks.
Вызов Zeebe
С помощью API клиента Zeebe вы можете взаимодействовать с движком процессов. Два наиболее важных вызова API — это запуск новых экземпляров процессов и корреляция сообщений с экземпляром процесса.
Запуск экземпляров процессов с использованием Java-клиента:
processInstance = zeebeClient.newCreateInstanceCommand()
.bpmnProcessId("someProcess").latestVersion()
.variables( someProcessVariablesAsMap )
.send()
.exceptionally( throwable -> { throw new RuntimeException("Could not create new instance", throwable); });
Запуск экземпляров процессов с использованием NodeJS-клиента:
const processInstance = await zbc.createWorkflowInstance({
bpmnProcessId: 'someProcess',
version: 5,
variables: {
testData: 'something',
}
})
Корреляция сообщений с экземплярами процессов с использованием Java-клиента:
zeebeClient.newPublishMessageCommand()
.messageName("messageA")
.messageId(uniqueMessageIdForDeduplication)
.correlationKey(message.getCorrelationid())
.variables(singletonMap("paymentInfo", "YeahWeCouldAddSomething"))
.send()
.exceptionally( throwable -> { throw new RuntimeException("Could not publish message " + message, throwable); });
Корреляция сообщений с экземплярами процессов с использованием NodeJS-клиента:
zbc.publishMessage({
name: 'messageA',
messageId: messageId,
correlationKey: correlationId,
variables: {
valueToAddToWorkflowVariables: 'here',
status: 'PROCESSED'
},
timeToLive: Duration.seconds.of(10)
})
Это позволяет вам подключить Zeebe к любой внешней системе, написав немного собственного glue-кода. Мы рассмотрим распространённые примеры технологий, чтобы проиллюстрировать это далее.
Подписка на задачи с использованием job worker
Чтобы реализовать сервисные задачи модели процесса, вы можете написать код, который подписывается на движок процессов. По сути, вы напишете glue-код, который будет вызываться каждый раз, когда достигается сервисная задача (которая внутренне создаёт задание, отсюда и название).
Glue-код на Java:
class ExampleJobHandler implements JobHandler {
public void handle(final JobClient client, final ActivatedJob job) {
// here: business logic that is executed with every job
client.newCompleteCommand(job.getKey()).send()
.exceptionally( throwable -> { throw new RuntimeException("Could not complete job " + job, throwable); });;
}
}
Glue-код на NodeJS:
function handler(job, complete, worker) {
// here: business logic that is executed with every job
complete.success()
}
Теперь этот обработчик нужно подключить к Zeebe, что обычно делается через подписки, которые опрашивают сервер для получения заданий (long polling).
Открытие подписки с использованием Zeebe Java-клиента:
zeebeClient
.newWorker()
.jobType("serviceA")
.handler(new ExampleJobHandler())
.timeout(Duration.ofSeconds(10))
.open()) {waitUntilSystemInput("exit");}
Открытие подписки с использованием Zeebe NodeJS-клиента:
zbc.createWorker({
taskType: 'serviceA',
taskHandler: handler,
})
Вы также можете использовать интеграции в некоторых программных фреймворках, таких как Spring Zeebe в мире Java, которая запускает job worker и реализует подписку автоматически в фоне для вашего glue-кода.
Подписка для glue-кода автоматически открывается через интеграцию Spring:
@ZeebeWorker(type = "serviceA")
public void handleJobFoo(final JobClient client, final ActivatedJob job) {
// here: business logic that is executed with every job
client.newCompleteCommand(job.getKey()).send()
.exceptionally( throwable -> { throw new RuntimeException("Could not complete job " + job, throwable); });;
}
Примеры технологий
Большинство проектов хотят подключаться к определённым технологиям, и сейчас чаще всего спрашивают про REST, обмен сообщениями или Kafka. Давайте посмотрим, как можно работать с этими технологиями.
REST
Вы можете написать фрагмент кода, который предоставляет REST endpoint на выбранном языке, и затем запускает экземпляр процесса.
Демо-проект Ticket Booking содержит пример с использованием Java и Spring Boot для REST endpoint. Аналогично, вы можете использовать расширение Spring Boot для запуска job worker’ов, которые выполняют исходящие REST-вызовы.

Вы можете найти пример кода на NodeJS для REST endpoint в примере Flowing Retail.
Обмен сообщениями
Вы можете сделать то же самое для сообщений, что сейчас часто означает использование AMQP.
Пример Ticket Booking содержит пример для RabbitMQ, Java и Spring Boot. В нем есть listener для сообщений, чтобы коррелировать входящие сообщения с ожидающими экземплярами процессов, и glue-код для отправки исходящих сообщений в брокер сообщений.

Обратите внимание, что поддержка элемента send task, используемого в этой диаграмме, будет добавлена в Camunda Cloud 1.1 в июле 2021 года. До версии 1.1 вам, возможно, придётся использовать service task в качестве обходного пути.
В следующем посте этой серии вы узнаете, почему я использовал send и receive task здесь, а не просто service task, который технически тоже бы сработал (спойлер: потому что сервис оплаты может выполняться долго — например, если нужно обновить истёкшие кредитные карты или выполнить банковский перевод).
Та же концепция применима и к другим языкам программирования — например, вы можете использовать клиент NodeJS для RabbitMQ и клиент NodeJS для Zeebe, чтобы создать тот же тип glue-кода, как показано выше.
Apache Kafka
Вы можете использовать тот же подход с Kafka-топиками. Пример Flowing Retail показывает это с использованием Java, Spring Boot и Spring Cloud Streams. В нём есть код для подписки на Kafka-топик и запуска новых экземпляров процессов при получении новых записей, а также glue-код для создания новых записей при выполнении service task. Конечно, вы можете использовать и другие фреймворки для достижения того же результата.

Построение приложений с glue-кодом
Обычные приложения включают несколько участков glue-кода в одной кодовой базе.

Например, микросервис онбординга, показанный на рисунке выше, включает:
REST endpoint, запускающий экземпляр процесса (1)
саму схему процесса (2), вероятно, автоматически развёрнутую в движке процессов при старте приложения
glue-код, подписывающийся на две service tasks, которые вызывают удалённый REST API (3) и (4)
Job worker будет автоматически запущен как часть приложения для обработки подписок. В этом примере приложение написано на Java, но, опять же, это может быть любой поддерживаемый язык программирования.
Использование готовых коннекторов или мостов
Как вы уже увидели, glue-код довольно прост, но вам приходится писать код. Иногда может быть предпочтительнее использовать готовый компонент, который подключает Zeebe к нужной технологии просто за счёт конфигурации. Такой компонент называется «connector» или «bridge» (я поясню терминологию ниже, оба термина можно считать синонимами).
Коннектор может быть одно- или двусторонним и обычно представляет собой отдельное приложение, реализующее соединение и трансляцию в одном или обоих направлениях. Такой коннектор может быть полезен и в случаях, когда интеграция становится сложной.

Например, HTTP-коннектор — это односторонний коннектор, содержащий job worker, который может обрабатывать сервисные задачи, выполняющие HTTP-запросы, как показано на следующей иллюстрации.

Другой пример — Kafka-коннектор, как показано ниже.

Это двусторонний коннектор, который содержит Kafka-listener для пересылки Kafka-записей в Zeebe, а также job worker, создающий Kafka-записи каждый раз, когда выполняется service task. Это показано на следующем примере.

Использование коннекторов в Camunda Cloud
На данный момент коннекторы не управляются как часть Camunda Cloud, что означает, что вам нужно управлять ими самостоятельно в вашей среде — будь то частное или публичное облако.

Плюсы и минусы коннекторов
В общем, коннекторы — интересный способ решения сложных интеграций, где не требуется кастомизация. Например, мост Camunda RPA для подключения RPA-ботов (вскоре будет доступен для Camunda Cloud).
Коннекторы хорошо подходят для сценариев, где не нужен собственный glue-код, например, при оркестрации serverless-функций AWS с AWS Lambda Connector. Такой коннектор можно один раз запустить и использовать в разных процессах.
Но у коннекторов есть и общие недостатки:
Возможности ограничены тем, что предусмотрел автор коннектора. На практике у вас могут быть немного другие требования, и вы вскоре столкнётесь с ограничениями коннектора.
Коннектор требует, чтобы вы управляли им в дополнение к вашему собственному приложению. Сложность, связанная с этим, зависит от вашей среды.
Тестировать ваш glue-код становится труднее, так как вы не можете легко подключить mock-объекты в такой коннектор — в отличие от собственного glue-кода.
Общее эмпирическое правило: предпочитайте собственный glue-код, если у вас нет веской причины использовать существующий коннектор (как описано выше).
Переиспользование собственной интеграционной логики через выделение коннекторов
Если вам нужно регулярно интегрироваться с определённой инфраструктурой, например, с вашей CRM-системой, вы можете захотеть создать собственный CRM-коннектор, централизованно его развернуть и использовать в разных приложениях.
В целом мы не рекомендуем начинать с таких коннекторов слишком рано. Не забывайте, что такой коннектор становится трудно настраиваемым после запуска в продакшене и при использовании в нескольких приложениях. Также часто гораздо сложнее правильно извлечь все параметры конфигурации и передать их из процесса, чем просто написать собственный glue-код на нужном языке программирования.
Так что стоит выделять полноценный коннектор только в том случае, если вы точно понимаете, что вам нужно.
Не забывайте и о возможности выделения общего glue-кода в простую библиотеку, которую можно использовать в разных местах. Но учтите, что обновление библиотеки, используемой в различных других приложениях, может быть сложнее, чем обновление одного централизованного коннектора. Как и во всём в жизни, лучший подход зависит от вашей ситуации.
Но если у вас уже есть такой glue-код в работе, и вы действительно понимаете последствия превращения его в коннектор, а также ту ценность, которую это принесёт — это вполне может иметь смысл.
Глоссарий
Я не хочу завершать этот пост, не подведя итоги по используемой терминологии (вам также может быть интересно заглянуть в глоссарий Camunda Cloud):
Bridge: синоним «connector».
Connector: программный компонент, который соединяет Zeebe с другой системой или инфраструктурой. Может быть одно- или двусторонним и возможно включает job worker. Граница между connector и job worker может быть размыта — в целом, коннекторы подключаются к другим активным программным компонентам. Например, «DMN connector» может подключать Zeebe к управляемому DMN‑движку, а «DMN worker» будет использовать DMN-библиотеку для выполнения решений.
Glue Code: любой программный код, связанный с моделью процесса (например, обработчик job worker’а).
Job Worker: активный программный компонент, который подписывается на Zeebe для выполнения доступных заданий (обычно когда экземпляр процесса достигает service task).
Worker: синоним «job worker».
Заключение
Этот блог-пост провёл вас по основам подключения Zeebe — движка процессов в составе Camunda Cloud — к вашему окружению. В основном это касается написания собственного glue-кода на предпочитаемом вами языке программирования и использования существующих клиентских библиотек. В некоторых случаях вы также можете захотеть использовать готовые коннекторы или мосты — по крайней мере, как отправную точку.
