Внешние задачи
|
Этот раздел перенесён из документации Camunda 7 и в дальнейшем будет доработан с учётом особенностей OpenBPM Engine |
Движок процессов поддерживает два способа выполнения сервисных задач:
-
Внутренние сервисные задачи: синхронный вызов кода, развернутого вместе с процессным приложением
-
Внешние задачи: предоставление единицы работы в списке, который могут опрашивать воркеры
Первый вариант используется, когда код реализован как код делегирования или как скрипт. В отличие от этого, внешние (сервисные) задачи работают так, что движок процессов публикует единицу работы, которую воркер может получить и завершить. Мы называем это паттерном внешних задач.
Обратите внимание, что указанное выше различие не говорит о том, где фактически реализована «бизнес-логика»: локально или как удаленный сервис. Java Delegate, вызываемый внутренней сервисной задачей, может либо сам реализовывать бизнес-логику, либо обращаться к web/rest-сервису, отправлять сообщение в другую систему и т. д. То же самое верно и для внешнего воркера. Воркер может реализовывать бизнес-логику напрямую или, опять же, делегировать ее удаленной системе.
Паттерн внешних задач
Поток выполнения внешних задач концептуально можно разделить на три шага, как показано на следующем изображении:

-
Движок процессов: создание экземпляра внешней задачи
-
Внешний воркер: получение и блокировка внешних задач
-
Внешний воркер и движок процессов: завершение экземпляра внешней задачи
Когда движок процессов встречает сервисную задачу, настроенную на внешнюю обработку, он создает экземпляр внешней задачи и добавляет его в список внешних задач (шаг 1). Экземпляр задачи получает topic, который определяет характер работы, которую нужно выполнить. Позже внешний воркер может получить и заблокировать задачи для определенного набора топиков (шаг 2). Чтобы одна и та же задача не была получена несколькими воркерами одновременно, у задачи есть блокировка на основе временной метки, которая устанавливается при захвате задачи. Только после истечения блокировки другой воркер сможет снова получить эту задачу. Когда внешний воркер завершил требуемую работу, он может сигнализировать движку процессов о продолжении выполнения процесса после сервисной задачи (шаг 3).
|
Внешние задачи концептуально очень похожи на пользовательские задачи. Если вы только начинаете разбираться в паттерне внешних задач, полезно провести аналогию с пользовательскими задачами: Пользовательские задачи создаются движком процессов и добавляются в список задач. Затем движок ожидает, что пользователь запросит список, возьмет задачу в работу и завершит ее. Внешние задачи похожи: внешняя задача создается и затем добавляется в топик. Внешнее приложение затем запрашивает топик и блокирует задачу. После блокировки приложение может работать над задачей и завершить ее. |
Суть этого паттерна в том, что сущности, выполняющие фактическую работу, независимы от движка процессов и получают единицы работы через опрос API движка процессов. Это дает следующие преимущества:
-
Преодоление границ систем: внешнему воркеру не нужно выполняться в том же Java-процессе, на той же машине, в том же кластере или даже на том же континенте, что и движок процессов. Нужно только иметь доступ к API движка процессов (через REST или Java). Благодаря паттерну опроса воркеру не нужно предоставлять интерфейс, к которому должен обращаться движок процессов.
-
Преодоление границ технологий: внешний воркер не обязан быть реализован на Java. Можно использовать любую технологию, которая лучше подходит для выполнения единицы работы и может обращаться к API движка процессов (через REST или Java).
-
Специализированные воркеры: внешний воркер не обязан быть приложением общего назначения. Каждый экземпляр внешней задачи получает имя топика, определяющее характер выполняемой задачи. Воркеры могут опрашивать задачи только тех топиков, с которыми они умеют работать.
-
Гранулярное масштабирование: если высокая нагрузка сосредоточена на обработке сервисных задач, количество внешних воркеров для соответствующих топиков можно масштабировать независимо от движка процессов.
-
Независимое обслуживание: воркеры можно обслуживать независимо от движка процессов без нарушения работы. Например, если у воркера для определенного топика возник простой (например, из-за обновления), это не оказывает немедленного влияния на движок процессов. Выполнение внешних задач для таких воркеров деградирует плавно: они сохраняются в списке внешних задач до тех пор, пока внешний воркер не возобновит работу.
Работа с внешними задачами
Чтобы работать с внешними задачами, их нужно объявить в BPMN XML. Во время выполнения к экземплярам внешних задач можно обращаться через Java API и REST API. Далее объясняются концепции API с акцентом на Java API. Во многих случаях в этом контексте лучше подходит REST API, особенно при реализации воркеров, работающих в разных окружениях и на разных технологиях.
BPMN
В BPMN XML определения процесса сервисную задачу можно объявить как выполняемую внешним воркером с помощью атрибутов camunda:type и camunda:topic. Например, сервисная задача Validate Address может быть настроена так, чтобы предоставлять экземпляр внешней задачи для топика AddressValidation:
<serviceTask id="validateAddressTask"
name="Validate Address"
camunda:type="external"
camunda:topic="AddressValidation" />
Имя топика можно задать не константным значением, а с помощью выражения.
Кроме того, по паттерну внешних задач можно реализовать и другие элементы, похожие на сервисные задачи, например send tasks, business rule tasks и throwing message events. Подробности см. в справочнике по реализации BPMN 2.0.
Определения событий ошибки
Внешние задачи позволяют определять события ошибки, которые выбрасывают указанный BPMN error. Это можно сделать, добавив элемент расширения camunda:errorEventDefinition в определение задачи. В отличие от bpmn:errorEventDefinition, элементы camunda:errorEventDefinition принимают дополнительный атрибут expression, который поддерживает любое JUEL-выражение. Внутри выражения через ключ externalTask доступен объект {{< javadocref page="org/camunda/bpm/engine/externaltask/ExternalTask.html" text="ExternalTaskEntity" >}}, который предоставляет getter-методы
для errorMessage, errorDetails, workerId, retries и других полей.
Выражение вычисляется при вызовах ExternalTaskService#complete и
ExternalTaskService#handleFailure. Если выражение вычисляется в true, фактическое выполнение метода отменяется и заменяется выбрасыванием соответствующего BPMN error. Эту ошибку может перехватить
граничное событие ошибки. Это означает, что определение события ошибки можно использовать как в сценариях успеха, так и неуспеха: даже если задача завершена успешно, вы все равно можете решить выбросить BPMN error.
<serviceTask id="validateAddressTask"
name="Validate Address"
camunda:type="external"
camunda:topic="AddressValidation" >
<extensionElements>
<camunda:errorEventDefinition id="addressErrorDefinition"
errorRef="addressError"
expression="${externalTask.getErrorDetails().contains('address error found')}" />
</extensionElements>
</serviceTask>
Дополнительную информацию о работе определений событий ошибки для внешних задач см. в руководстве пользователя по языку выражений.
Rest API
См. {{< restref text="документацию REST API" tag="External-Task" >}}, чтобы узнать, как обращаться к операциям API через HTTP.
Длинный опрос для получения и блокировки внешних задач
Обычные HTTP-запросы сервер обрабатывает немедленно, независимо от того, доступна ли запрошенная информация или нет. Это неизбежно приводит к ситуации, когда клиенту приходится выполнять множественные повторяющиеся запросы, пока информация не станет доступной (polling). Такой подход, очевидно, может быть затратным с точки зрения ресурсов.

С помощью long polling запрос приостанавливается сервером, если внешние задачи недоступны. Как только появляются новые внешние задачи, запрос возобновляется и выполняется ответ. Приостановка ограничена настраиваемым промежутком времени (timeout).
Long polling существенно сокращает количество запросов и позволяет более эффективно использовать ресурсы как на стороне сервера, так и на стороне клиента.
См. также {{< restref page="fetchAndLock" text="документацию REST API" tag="External-Task" >}}.
Уникальный запрос воркера
По умолчанию несколько воркеров могут использовать один и тот же workerId. Чтобы обеспечить уникальность workerId на стороне сервера,
можно включить флаг Unique Worker Request. Этот флаг конфигурации влияет только на long-polling запросы, но не на обычные
запросы Fetch and Lock. Если флаг Unique Worker Request включен, ожидающие запросы с тем же workerId
отменяются при получении нового запроса.
Чтобы включить флаг Unique Worker Request, необходимо изменить файл engine-rest/WEB-INF/web.xml, входящий в артефакт engine-rest,
установив контекстный параметр fetch-and-lock-unique-worker-request в true. Рассмотрите
следующий фрагмент конфигурации:
<!-- ... -->
<context-param>
<param-name>fetch-and-lock-unique-worker-request</param-name>
<param-value>true</param-value>
</context-param>
<!-- ... -->
Емкость блокирующей очереди
По умолчанию блокирующая очередь long-polling запросов Fetch and Lock имеет емкость 200, то есть при превышении этого количества запросов будет возвращена ошибка. Это значение можно изменить, установив свойство fetch-and-lock-queue-capacity как контекстный параметр в файле engine-rest/WEB-INF/web.xml артефакта engine-rest. Если это свойство отсутствует или его значение некорректно, будет использовано значение по умолчанию.
Рассмотрите следующий фрагмент конфигурации:
<!-- ... -->
<context-param>
<param-name>fetch-and-lock-queue-capacity</param-name>
<param-value>250</param-value>
</context-param>
<!-- ... -->
Java API
Точкой входа в Java API для внешних задач является ExternalTaskService. Получить его можно через processEngine.getExternalTaskService().
Ниже приведен пример взаимодействия, который получает 10 задач, обрабатывает эти задачи в цикле и для каждой задачи либо завершает ее, либо помечает как завершившуюся ошибкой.
List<LockedExternalTask> tasks = externalTaskService.fetchAndLock(10, "externalWorkerId")
.topic("AddressValidation", 60L * 1000L)
.execute();
for (LockedExternalTask task : tasks) {
try {
String topic = task.getTopicName();
// work on task for that topic
...
// if the work is successful, mark the task as completed
if(success) {
externalTaskService.complete(task.getId(), variables);
}
else {
// if the work was not successful, mark it as failed
externalTaskService.handleFailure(
task.getId(),
"externalWorkerId",
"Address could not be validated: Address database not reachable",
1, 10L * 60L * 1000L);
}
}
catch(Exception e) {
//... handle exception
}
}
В следующих разделах различные взаимодействия с ExternalTaskService рассматриваются более подробно.
Получение задач
Чтобы реализовать воркер с опросом, операцию получения можно выполнить методом ExternalTaskService#fetchAndLock. Этот метод возвращает fluent builder, который позволяет определить набор топиков для получения задач. Рассмотрим следующий фрагмент кода:
List<LockedExternalTask> tasks = externalTaskService.fetchAndLock(10, "externalWorkerId")
.topic("AddressValidation", 60L * 1000L)
.topic("ShipmentScheduling", 120L * 1000L)
.execute();
for (LockedExternalTask task : tasks) {
String topic = task.getTopicName();
// work on task for that topic
...
}
Этот код получает не более 10 задач топиков AddressValidation и ShipmentScheduling. Результирующие задачи эксклюзивно блокируются для воркера с id externalWorkerId. Блокировка означает, что задача резервируется за этим воркером на определенную длительность, начиная с момента получения, и предотвращает получение той же задачи другим воркером, пока блокировка действительна. Если блокировка истекает, а задача к этому моменту не завершена, ее может получить другой воркер, чтобы воркеры, которые «тихо» перестали работать, не блокировали выполнение на неопределенное время. Точная длительность задается в инструкциях получения по каждому топику: задачи для AddressValidation блокируются на 60 секунд (60L * 1000L миллисекунд), а задачи для ShipmentScheduling - на 120 секунд (120L * 1000L миллисекунд). Длительность блокировки не должна быть меньше ожидаемого времени выполнения. Она также не должна быть слишком большой, если это приводит к слишком долгому таймауту до повторной попытки задачи при «тихом» падении воркера.
Переменные, необходимые для выполнения задачи, могут быть получены вместе с задачей. Например, предположим, что задаче AddressValidation нужна переменная address. Получение задач с этой переменной может выглядеть так:
List<LockedExternalTask> tasks = externalTaskService.fetchAndLock(10, "externalWorkerId")
.topic("AddressValidation", 60L * 1000L).variables("address")
.execute();
for (LockedExternalTask task : tasks) {
String topic = task.getTopicName();
String address = (String) task.getVariables().get("address");
// work on task for that topic
...
}
Тогда результирующие задачи будут содержать текущие значения запрошенной переменной. Обратите внимание, что это значения переменных, видимые в иерархии областей видимости из execution внешней задачи. Подробности см. в главе Области видимости переменных и видимость переменных.
Чтобы получить все переменные, вызов метода variables() нужно опустить.
List<LockedExternalTask> tasks = externalTaskService.fetchAndLock(10, "externalWorkerId")
.topic("AddressValidation", 60L * 1000L)
.execute();
for (LockedExternalTask task : tasks) {
String topic = task.getTopicName();
String address = (String) task.getVariables().get("address");
// work on task for that topic
...
}
Чтобы включить десериализацию значений сериализованных переменных (обычно переменных, хранящих пользовательские Java-объекты), необходимо вызвать enableCustomObjectDeserialization(). Иначе при попытке получить сериализованную переменную из map переменных будет выброшено исключение о том, что объект не десериализован.
List<LockedExternalTask> tasks = externalTaskService.fetchAndLock(10, "externalWorkerId")
.topic("AddressValidation", 60L * 1000L)
.variables("address")
.enableCustomObjectDeserialization()
.execute();
for (LockedExternalTask task : tasks) {
String topic = task.getTopicName();
MyAddressClass address = (MyAddressClass) task.getVariables().get("address");
// work on task for that topic
...
}
Приоритизация внешних задач
Приоритизация внешних задач похожа на приоритизацию job. Существует та же проблема starvation, которую следует учитывать. Подробнее см. раздел Приоритизация job.
Настройка движка процессов для приоритетов внешних задач
В этом разделе объясняется, как включать и отключать приоритеты внешних задач в конфигурации. В конфигурации движка процессов есть два релевантных свойства:
producePrioritizedExternalTasks: управляет тем, назначает ли движок процессов приоритеты внешним задачам. Значение по умолчанию - true.
Если приоритеты не нужны, свойство конфигурации движка процессов producePrioritizedExternalTasks можно установить в false. В этом случае все внешние задачи получат приоритет 0.
Подробности о том, как задавать приоритеты внешних задач и как движок процессов их назначает, см. в следующем разделе «Задание приоритетов внешних задач» link:{{< relref "#specify-external-task-priorities" >}}[].
Задание приоритетов внешних задач
Приоритеты внешних задач можно задавать в BPMN-модели, а также переопределять во время выполнения через API.
Приоритеты в BPMN XML
Приоритеты внешних задач можно назначать на уровне процесса и на уровне активности. Для этого можно использовать атрибут расширения OpenBPM Engine camunda:taskPriority.
Для задания приоритета поддерживаются как константные значения, так и выражения.
При использовании константного значения один и тот же приоритет назначается всем экземплярам процесса или активности.
Выражения, наоборот, позволяют назначать разный приоритет каждому экземпляру процесса или активности. Выражение должно вычисляться в число в диапазоне Java long.
Конкретное значение может быть результатом сложного вычисления и основываться на данных, предоставленных пользователем (из task form или других источников).
Приоритеты на уровне процесса
При настройке приоритетов внешних задач на уровне экземпляра процесса атрибут camunda:taskPriority нужно применить к элементу bpmn <process …>:
<bpmn:process id="Process_1" isExecutable="true" camunda:taskPriority="8">
...
</bpmn:process>
Эффект в том, что все внешние задачи внутри процесса наследуют одинаковый приоритет (если он не переопределен локально). В приведенном выше примере показано, как для установки приоритета использовать константное значение. Таким образом один и тот же приоритет применяется ко всем экземплярам процесса. Если разные экземпляры процесса должны выполняться с разными приоритетами, можно использовать выражение:
<bpmn:process id="Process_1" isExecutable="true" camunda:taskPriority="${order.priority}">
...
</bpmn:process>
В примере выше приоритет определяется на основе свойства priority переменной order.
Приоритеты на уровне сервисной задачи
При настройке приоритетов внешних задач на уровне сервисной задачи атрибут camunda:taskPriority нужно применить к элементу bpmn <serviceTask …>.
Сервисная задача должна быть внешней задачей с атрибутом camunda:type="external".
...
<serviceTask id="externalTaskWithPrio"
camunda:type="external"
camunda:topic="externalTaskTopic"
camunda:taskPriority="8"/>
...
Эффект в том, что приоритет задается для определенной внешней задачи (переопределяет taskPriority процесса). В приведенном выше примере показано, как использовать константное значение для установки приоритета. Таким образом один и тот же приоритет применяется к внешней задаче в разных экземплярах процесса. Если разные экземпляры процесса должны выполняться с разными приоритетами внешних задач, можно использовать выражение:
...
<serviceTask id="externalTaskWithPrio"
camunda:type="external"
camunda:topic="externalTaskTopic"
camunda:taskPriority="${order.priority}"/>
...
В примере выше приоритет определяется на основе свойства priority переменной order.
Получение внешней задачи
По приоритету
Чтобы получать внешние задачи на основе их приоритета, можно использовать перегруженный метод ExternalTaskService#fetchAndLock с параметром usePriority.
Метод без булевого параметра возвращает внешние задачи в произвольном порядке. Если параметр задан, возвращаемые внешние задачи сортируются по убыванию.
См. следующий пример, учитывающий приоритет внешних задач:
List<LockedExternalTask> tasks = externalTaskService.fetchAndLock(10, "externalWorkerId", true)
.topic("AddressValidation", 60L * 1000L)
.topic("ShipmentScheduling", 120L * 1000L)
.execute();
for (LockedExternalTask task : tasks) {
String topic = task.getTopicName();
// work on task for that topic
...
}
По времени создания
Внешние задачи также можно получать по их createTime в порядке LIFO или FIFO. Это поведение позволяет клиентам оптимизировать обработку и избегать starvation в сценариях, где «возраст» задач и их потребление не согласованы.
Метод ExternalTaskService#fetchAndLock() можно комбинировать со следующими методами для настройки порядка:
asc() - задачи будут отсортированы по возрастанию. Первая задача (с нулевым индексом) будет иметь самое раннее время, а последняя - самое старое.
desc() - задачи будут отсортированы по убыванию. Первая задача (с нулевым индексом) будет иметь самое старое время, а последняя - самое раннее.
См. следующий пример получения задач по createTime в порядке убывания:
List<LockedExternalTask> tasks = externalTaskService.fetchAndLock()
.workerId("worker")
.maxTasks(10)
.orderByCreateTime().desc()
.subscribe()
.topic("AddressValidation", 60L * 1000L)
.topic("ShipmentScheduling", 120L * 1000L)
.execute();
for (LockedExternalTask task : tasks) {
String topic = task.getTopicName();
// work on task for that topic
...
}
|
У внешних задач, созданных версиями движка < 7.21.0, атрибут |
Многоуровневая сортировка
При получении внешних задач можно комбинировать несколько критериев сортировки. Например, передача true в параметр usePriority и выбор эффективного значения сортировки для конфигурации createTime приводят к тому, что внешние задачи сначала сортируются по убыванию приоритета; если две задачи имеют одинаковый приоритет, для сортировки результатов с равным приоритетом используется выбранный порядок createTime.
Ниже пример, иллюстрирующий описанное выше:
Даны следующие условные задачи:
ExternalTask1 [priority=0, createTime=1]
ExternalTask2 [priority=2, createTime=2]
ExternalTask3 [priority=0, createTime=3]
ExternalTask4 [priority=3, createTime=4]
И следующая конфигурация вызова с сортировкой по priority и createTime:
externalTaskService.fetchAndLock()
.maxTasks(10)
.workerId("worker")
.usePriority(true)
.orderByCreateTime().desc();
Результаты будут возвращены в следующем порядке:
ExternalTask4 [priority=3, createTime=4]
ExternalTask2 [priority=2, createTime=2]
ExternalTask3 [priority=0, createTime=3]
ExternalTask1 [priority=0, createTime=1]
Примечание: поле createTime, используемое в примере, представлено числами для наглядности. В реальных результатах createTime будет заполнено значением Date.
{{< note title="" class="info" >}}
Priority всегда будет иметь приоритет над любым другим свойством сортировки.
Завершение задач
После получения задачи и выполнения требуемой работы воркер может завершить внешнюю задачу вызовом метода ExternalTaskService#complete. Воркер может завершать только те задачи, которые он ранее получил и заблокировал. Если задача тем временем была заблокирована другим воркером, будет выброшено исключение.
|
Внешние задачи могут включать определения событий ошибки, которые могут отменить выполнение |
Продление блокировок внешних задач
Когда внешняя задача заблокирована воркером, длительность блокировки можно продлить вызовом метода ExternalTaskService#extendLock. Воркер может указать время (в миллисекундах), на которое нужно обновить таймаут. Блокировку может продлевать только воркер, который владеет блокировкой заданной внешней задачи.
Сообщение о сбое задачи
Воркер не всегда может успешно завершить задачу. В этом случае он может сообщить о сбое движку процессов с помощью ExternalTaskService#handleFailure. Как и #complete, #handleFailure может быть вызван только воркером, владеющим самой актуальной блокировкой задачи. Метод #handleFailure принимает четыре дополнительных аргумента: errorMessage,errorDetails, retries, retryTimeout. Сообщение об ошибке может содержать описание природы проблемы и ограничено 666 символами. К нему можно получить доступ при повторном получении или запросе задачи. Поле errorDetails может содержать полное описание ошибки и не ограничено по длине. Подробности ошибки доступны через отдельный метод ExternalTaskService#getExternalTaskErrorDetails по параметру id задачи. С помощью retries и retryTimout воркеры могут задать стратегию повторных попыток. Если установить retries в значение > 0, задачу можно снова получить после истечения retryTimeout. Если установить retries в 0, задача больше не может быть получена, и для нее создается incident.
Рассмотрим следующий фрагмент кода:
List<LockedExternalTask> tasks = externalTaskService.fetchAndLock(10, "externalWorkerId")
.topic("AddressValidation", 60L * 1000L).variables("address")
.execute();
LockedExternalTask task = tasks.get(0);
// ... processing the task fails
externalTaskService.handleFailure(
task.getId(),
"externalWorkerId",
"Address could not be validated: Address database not reachable", // errorMessage
"Super long error details", // errorDetails
1, // retries
10L * 60L * 1000L); // retryTimeout
// ... other activities
externalTaskService.getExternalTaskErrorDetails(task.getId());
Для заблокированной задачи сообщается сбой, так что ее можно повторить еще один раз через 10 минут. Движок процессов сам не уменьшает retries. Вместо этого такое поведение можно реализовать, устанавливая retries в task.getRetries() - 1 при сообщении о сбое.
В момент, когда нужны подробности ошибки, они запрашиваются у сервиса отдельным методом.
|
Внешние задачи могут включать определения событий ошибки, которые могут отменить выполнение |
Сообщение о BPMN-ошибке
См. документацию по граничным событиям ошибки.
В ходе выполнения может возникнуть бизнес-ошибка. В этом случае воркер может сообщить о BPMN-ошибке движку процессов с помощью ExternalTaskService#handleBpmnError.
Как и #complete или #handleFailure, этот метод может быть вызван только воркером, владеющим самой актуальной блокировкой задачи.
Метод #handleBpmnError принимает один дополнительный аргумент: errorCode.
Код ошибки идентифицирует заранее определенную ошибку. Если переданный errorCode не существует или граничное событие не определено,
текущий экземпляр активности просто завершается, и ошибка не обрабатывается.
См. следующий пример:
List<LockedExternalTask> tasks = externalTaskService.fetchAndLock(10, "externalWorkerId")
.topic("AddressValidation", 60L * 1000L).variables("address")
.execute();
LockedExternalTask task = tasks.get(0);
// ... business error appears
externalTaskService.handleBpmnError(
task.getId(),
"externalWorkerId",
"bpmn-error", // errorCode
"Thrown BPMN Error during...", // errorMessage
variables);
Распространяется BPMN-ошибка с кодом bpmn-error. Если существует граничное событие с этим кодом ошибки, BPMN-ошибка будет перехвачена и обработана.
Сообщение об ошибке и переменные являются необязательными. Они могут предоставить дополнительную информацию об ошибке. Если BPMN-ошибка будет перехвачена, переменные будут переданы в execution.
Запрос задач
Запрос внешних задач можно выполнить через ExternalTaskService#createExternalTaskQuery. В отличие от #fetchAndLock, это запрос чтения, который не устанавливает блокировки.
Операции управления
Дополнительные операции управления: ExternalTaskService#unlock, ExternalTaskService#setRetries и ExternalTaskService#setPriority - сбросить текущую блокировку, установить retries и установить приоритет внешней задачи.
Установка retries полезна, когда у задачи осталось 0 повторных попыток и ее нужно возобновить вручную. Последним методом приоритет можно
повысить для более важных или понизить для менее важных внешних задач.
Также есть операции ExternalTaskService#setRetriesSync и ExternalTaskService#setRetriesAsync для установки retries сразу для нескольких внешних задач синхронно или асинхронно.
Лицензия и атрибуция
Эта документация была создана на базе материала "Camunda 7 Docs" от Camunda, находится под лицензией Creative Commons Attribution-ShareAlike 3.0 Unported License .
Оригинал документации: https://docs.camunda.org