Введенный в 2015 году, паттерн внешней задачи (external task pattern) становится всё более популярным. Вместо того чтобы движок процессов активно вызывал какой-либо код (push), паттерн внешней задачи добавляет работу в некую очередь и позволяет воркерам (workers) извлекать их по мере необходимости. Этот метод также известен как публикация/подписка (publish/subscribe). Движок процессов публикует задачи, а воркеры подписываются на их выполнение.

В рамках Camunda Platform 7 мы работаем над тем, чтобы паттерн внешних задач стал стандартной рекомендацией, а для Camunda Cloud это вообще единственный способ написания связующего кода. В частности, использование Java-делегатов больше не поддерживается в Camunda Cloud. Это иногда ставит людей в тупик, поэтому этот пост ответит на вопрос о том, почему это не является проблемой, и подробно рассмотрит преимущества, которые вы можете получить от внешних задач. Этот пост также развенчает некоторые мифы об этом паттерне:
Вы все равно можете вызывать конечные точки ваших сервисов через любой протокол (например, REST, AMQP, Kafka).
Весь код воркеров может находиться в одном Java-приложении.
Модель программирования оказывается удивительно похожей на Java-делегаты при использовании Spring.
Обработка исключений также может быть доверена BPM-движку.
Накладные расходы на производительность невелики.
Архитектурные соображения
Давайте развенчаем два архитектурных мифа, связанных с внешними задачами.
Во-первых, использование внешних задач не означает, что сервисы, которые вы раньше вызывали через REST, теперь должны самостоятельно извлекать свои задачи. Хотя это является архитектурным вариантом, это не типичный случай. Рассмотрим пример:

Вы, вероятно, реализуете воркера, который всё равно будет выполнять REST-вызов к микросервису обработки платежей (левая сторона иллюстрации выше). Это API, которое микросервис предоставляет, и его следует использовать. Воркеры находятся в рамках решения по обработке заказов или микросервиса. Никто за пределами команды по обработке заказов не должен даже знать, что используется Camunda или воркер внешней задачи.
Сравните это с решением на правой стороне примера, где микросервис обработки платежей напрямую извлекает свои задачи из Camunda. В этом случае Camunda выступает в качестве посредника (middleware), используемого для взаимодействия различных микросервисов. Хотя это возможно и имеет свои преимущества, я редко встречал такие решения на практике.
Второй миф заключается в том, что вам нужно писать несколько приложений, если у вас есть несколько сервисных задач, по одному для каждого воркера вне��ней задачи. Хотя вы можете разделить воркеров на несколько приложений, это встречается редко. Гораздо более распространено запускать всех (или, по крайней мере, большинство) ваших воркеров в одном приложении.
Это приложение логически связано с решением по автоматизации процессов и регистрирует воркера для каждой внешней задачи. Оно также способно автоматически развертывать модель процесса.
Написание связующего кода (glue code)
Это подводит нас к вопросу о том, как писать связующий код. В этой области существует ещё один миф: это должно быть сложно, поскольку задействована удалённая связь. Хорошая новость заключается в том, что это не обязательно верно для Camunda Cloud, так как существуют клиентские библиотеки для различных языков программирования, которые обеспечивают отличное взаимодействие для разработчиков.
Например, используя интеграцию с Spring, вы можете написать код воркера следующим образом:
public class RetrieveMoneyWorker {
@JobWorker(type = "retrieveMoney", autoComplete = false)
public void retrieveMoney(final JobClient client, final ActivatedJob job) {
// Your logic here
client.newCompleteCommand(job.getKey()).send();
}
}
public class FetchGoodsWorker {
@JobWorker(type = "fetchGoods", autoComplete = false)
public void fetchGoods(final JobClient client, final ActivatedJob job) {
// Your logic here
client.newCompleteCommand(job.getKey()).send();
}
}Если сравнить этот код с Java-делегатом, он выглядит удивительно похоже. Мы даже создали расширение для сообщества, содержащее адаптер для повторного использования существующих Java-делегатов в Camunda Cloud. Хотя я не стал бы настоятельно рекомендовать это, так как лучше вручную мигрировать ваши классы, это хорошее подтверждение того, что концептуально это не так уж и сложно.
Таким образом, стоит отметить, что есть некоторые вещи, которые вы могли делать в Java-делегатах, но которые больше недоступны в воркерах внешних задач:
Доступ к внутренним механизмам движка процессов
Влияние на поведение движка процессов
Интеграция с пулом потоков или транзакциями движка процессов
Использование «грязных» трюков с помощью ThreadLocal или подобных подходов
В общем, я считаю, что это хорошо, что вы больше не можете выполнять эти действия, так как они регулярно приводили к проблемам.
Обработка исключений
При написании связующего кода вы также можете передавать проблемы внутри вашего воркера в движок процессов для их обработки. Например, движок процессов может инициировать повторную попытку выполнения или создание инцидента в операционных инструментах. Код довольно простой и, как и прежде, вполне сопоставим с Java-делегатами:
Read more on this in the spring-zeebe docs.
Тем не менее, существует один важный случай сбоя, который пока не решён должным образом: что делать, если воркер выходит из строя и больше не извлекает задачи? В настоящее время Camunda Cloud распознаёт это косвенно, когда сервисные задачи не обрабатываются слишком долго. Идеально было бы, чтобы сам движок процессов мог обнаружить, что задачи больше не извлекаются. В этом случае он мог бы указать на это в операционных инструментах. В настоящее время мы изучаем эту функцию. Пока вы можете полагаться на типичный системный мониторинг для обнаружения сбоя Java-приложения воркера.
А что насчёт транзакций?
Аналогично, как вы достигаете согласованности между вашим бизнес-кодом и движком процессов? Что делать, если какой-либо из компонентов выходит из строя? При использовании Java-делегатов многие пользователи передавали эти проблемы менеджеру транзакций, часто не осознавая этого. Пожалуйста, ознакомьтесь с постом о том, как добиться согласованности без менеджеров транзакций, чтобы узнать, как справляться с этим с внешними задачами, а также понять, почему это предпочтительная концепция на сегодняшний день.
Задержки при удалённой коммуникации
Последний миф, который я хотел бы обсудить в этом посте, заключается в том, что удалённые воркеры якобы должны быть «медленными». Часто в таких обсуждениях термин «медленный» не определ��ется, но если взглянуть на Camunda Platform 7, то в зависимости от конфигурации исполнителя задач действительно может потребоваться несколько секунд для того, чтобы внешняя задача была взята на выполнение (хотя это можно оптимизировать!) В Camunda Cloud вся взаимодействие оптимизировано изначально, так что добавляется лишь небольшая задержка из-за удалённой коммуникации. В недавнем эксперименте я измерил накладные расходы удалённого воркера и получил примерно 50 мс.

В большинстве проектов это вовсе не является проблемой, особенно поскольку это не влияет на пропускную способность движка процессов. Другими словами, вы по-прежнему можете обрабатывать то же количество экземпляров процессов, просто каждое выполнение сервисной задачи будет занимать на 50 мс дольше. Обратите внимание, что мы продолжаем оптимизировать это время для сценариев с низкой задержкой, которые востребованы среди наших клиентов.
Заключение
Как вы могли заметить, вы получаете модель программирования, которая столь же удобна, как и Java-делегаты. В то же время ваш код правильно изолирован от движка процессов (переход от встроенных к удалённым BPM-движкам открывает все преимущества удалённой конфигурации).
Вот почему я лично так рад переходу на внешние задачи в качестве стандартной модели связующего кода в Camunda.
Подписывайтесь на Telegram канал BPM Developers. Рассказываем про бизнес процессы: новост��, гайды, полезная информация и юмор.
5 февраля в 14:00 мск пройдет презентация новой open source платформы OpenBPM. Регистрируйтесь и приходите.
