Сервисная задача
Обзор
Сервисная задача используется для вызова Java-логики, внешних сервисов, API или автоматизированных приложений.
Сервисная задача изображается как прямоугольник со скругленными углами и небольшим значком шестеренки в левом верхнем углу.
В зависимости от реализации XML-представление сервисной задачи может различаться. Примеры см. в описаниях соответствующих типов задачи.
Свойства
В моделере BPMN можно задать следующие свойства сервисной задачи:
Свойство |
Описание |
Id |
Идентификатор элемента. Он генерируется автоматически, но его можно заменить на более понятный, чтобы логи было проще читать. |
Name |
Имя задачи, не более 255 символов. Обычно задачи называют по схеме «глагол + объект», например «Согласовать заказ» или «Отправить счет». |
Task type |
Способ вызова Java-логики для задачи. Поля ниже зависят от выбранного типа. См. Тип задачи. |
Bean name, Method |
Бин Spring и его метод для типа Spring Bean. См. Spring Bean. |
JavaDelegate class |
Класс для типа Java Delegate Class. См. Java Delegate Class. |
Expression |
Выражение для типа Expression. См. Expression. |
Delegate expression |
Выражение для типа Delegate Expression. См. Delegate Expression. |
Topic |
Топик для типа External Worker. См. External Worker. |
Result variable, Use local scope for result variable |
Переменная, в которой сохраняется результат метода или выражения. См. Переменная результата. |
Fields |
См. Инжектирование полей. |
Multi instance |
|
Execution listeners |
См. Слушатели выполнения. |
Extension properties |
См. Свойства расширения. |
Asynchronous execution |
Обновлено в Jmix 3.1 |
Failed job retry time cycle |
Доступно, если выбрано Asynchronous или Asynchronous leave. См. Повторные попытки при сбое. |
Documentation |
Описание и дополнительные сведения об элементе. |
|
Если выбрано Asynchronous и задача завершается ошибкой, предшествующие шаги, например выполнение пользовательской задачи, не откатываются и исполнитель заданий повторяет задачу. Задача выполняется позже в потоке исполнителя заданий, поэтому вызывающий код не ждет ее результата. |
Тип задачи
Task type определяет, как вызывается Java-логика сервисной задачи:
-
Spring Bean: укажите бин Spring, его метод и нужные параметры метода.
-
Java Delegate Class: укажите класс, который реализует
JavaDelegateилиFutureJavaDelegate. -
Expression: вызовите выражение-метод.
-
Delegate Expression: вычислите выражение, которое возвращает объект-делегат.
-
External Worker: создайте задание, которое забирает и выполняет внешний обработчик.
Spring Bean
Jmix BPM позволяет вызвать из сервисной задачи метод бина Spring и передать ему значения параметров.
Существующий бин и его метод можно выбрать в полях Bean Name и Method соответственно.
После выбора метода появляются дополнительные поля для ввода значений аргументов метода.
Обратите внимание на флажок is variable. Он имеет смысл в основном для строковых параметров. Если флажок не установлен, значение аргумента записывается в итоговое выражение в апострофах. Если флажок установлен, апострофы не добавляются и в метод передается переменная с указанным именем.
-
${smpl_MyBean.someMethod('description')}— это выражение использует строковое значениеdescription. -
${smpl_MyBean.someMethod(description)}— это выражение использует значение переменной с именемdescription.
Переменная результата
Если выбранный метод возвращает значение, появляется поле Result variable. В нем можно указать одну из существующих переменных процесса или создать новую, просто введя ее имя.
|
Следите за типами при использовании существующих переменных.
Если тип результата отличается от типа существующей переменной, создается новая переменная процесса с тем же именем.
Если была переменная |
Рядом с Result variable находится флажок Use local scope. Если флажок установлен, переменная результата, созданная сервисной задачей, получает локальную область в контексте исполнения задачи. Это значит, что переменная доступна только в текущем исполнении и не передается в родительское исполнение или экземпляр процесса.
Этот параметр помогает изолировать переменную в конкретном исполнении сервисной задачи. Если одновременно выполняется несколько экземпляров одной сервисной задачи, у каждого экземпляра будет своя локальная переменная, и они не будут мешать друг другу.
Вот как все параметры сервисной задачи с бином Spring представлены в XML:
<serviceTask id="set-status-service-task" name="Set order status"
flowable:async="true" (1)
flowable:expression="${smpl_OrderStatusBean.setStatus(orderId,'Sent')}" (2)
flowable:resultVariable="quantity" (3)
flowable:useLocalScopeForResultVariable="true" (4)
jmix:taskType="springBean" jmix:beanName="smpl_OrderStatusBean"> (5)
<extensionElements>
<jmix:springBean beanName="smpl_OrderStatusBean"
methodName="setStatus"> (6)
<jmix:methodParam name="orderId" type="java.lang.String"
isVariable="true">orderId</jmix:methodParam> (7)
<jmix:methodParam name="status" type="java.lang.String"
isVariable="false">Sent</jmix:methodParam> (8)
</jmix:springBean>
</extensionElements>
</serviceTask>
| 1 | флаг Asynchronous, по умолчанию false и не указывается. |
| 2 | сгенерированное выражение, апострофы заменены на '. |
| 3 | переменная результата. |
| 4 | флаг локальной области, по умолчанию false и не указывается. |
| 5 | тип задачи. |
| 6 | заданы имя бина Spring и метод. |
| 7 | параметр передается как переменная процесса. |
| 8 | параметр передается как значение. |
Переменная процесса execution
Бин Spring не видит контекст процесса. Но во многих случаях он нужен, например чтобы получить доступ к переменным процесса и свойствам текущей задачи.
Есть встроенная переменная процесса execution типа DelegateExecution, которую можно использовать как параметр метода бина Spring.
Создайте такой метод, например:
@Component("MyProcessBean")
public class MyProcessBean {
public void mySampleMethod(DelegateExecution execution) { (1)
String currentActivityId = execution.getCurrentActivityId();
Set<String> variableNames = execution.getVariableNames();
// etc.
}
}
| 1 | параметр execution |
Затем укажите этот метод в сервисной задаче:
Java Delegate Class
В этом случае бизнес-логику выполняет класс, который реализует интерфейс org.flowable.engine.delegate.JavaDelegate или org.flowable.engine.delegate.FutureJavaDelegate и переопределяет его метод execute(). Метод получает объект execution как параметр, поэтому у вас есть доступ к контексту процесса, включая все переменные процесса.
Если выбрать вариант JavaDelegate class в поле Task type, можно выбрать подходящий класс:
Например, реализуем класс, который создает переменную процесса со случайным значением:
public class RandomIndexJavaDelegate implements JavaDelegate {
@Override
public void execute(DelegateExecution execution) {
long randomIndex = new Random().nextLong(100L);
execution.setVariable("randomIndex", randomIndex);
}
}
Чтобы указать класс, вызываемый во время выполнения процесса,
нужно задать полное имя класса в атрибуте flowable:class.
<serviceTask id="Activity_java_delegate" name="Java delegate"
flowable:class="com.company.jmixbpmtraining.delegate.RandomIndexJavaDelegate" (1)
jmix:taskType="javaDelegateClass"> (2)
<extensionElements />
</serviceTask>
| 1 | указание класса Java Delegate. |
| 2 | определение типа задачи. |
Создание экземпляра класса Java Delegate
Классы, которые используются в сервисных задачах типа Java Delegate, не создаются при развертывании. Когда движок процессов впервые доходит до задачи во время выполнения, он создает экземпляр класса JavaDelegate.
Для сервисной задачи, в которой определен класс, создается только один экземпляр Java-класса. Если несколько сервисных задач процесса ссылаются на один и тот же класс Java Delegate, для каждой из них создается отдельный экземпляр. Все экземпляры процесса используют общий экземпляр класса для задачи.
Поэтому класс не должен использовать поля экземпляра и должен быть потокобезопасным, так как он может выполняться одновременно из разных потоков. Это также может влиять на инжектирование полей.
Delegate Expression
Выражение-делегат (delegate expression) — мощная возможность сервисных задач, которая позволяет динамически получать Java-объект во время выполнения.
Например, выражение ${myServiceBean} будет разрешено в бин Spring с именем myServiceBean.
В контексте Spring выражения-делегаты могут напрямую ссылаться на бины Spring, что обеспечивает интеграцию с фреймворком Spring. Это позволяет использовать инжектирование зависимостей и возможности Spring в реализации делегата.
Чтобы использовать классы-делегаты в выражениях-делегатах, добавьте к ним аннотацию @Component, как к бину Spring.
В этом случае класс сочетает возможности обоих типов — бина Spring и класса Java Delegate:
@Component
public class MyDelegateExpression implements JavaDelegate {
// Class fields and injections
@Override
public void execute(DelegateExecution execution) {
// Required logic
}
}
В результате из этого класса доступны контекст Spring и контекст процесса. Чтобы вызвать его, используйте тип задачи Delegate expression. Например:
Чтобы указать класс, вызываемый во время выполнения процесса, можно использовать выражение, которое возвращает объект.
Для этого в XML используется атрибут flowable:delegateExpression:
<serviceTask id="delegate-expression"
name="Delegate expression task"
flowable:delegateExpression="${MyDelegateExpression}"
jmix:taskType="delegateExpression">
</serviceTask>
Expression
Выражение — самый общий способ вызвать Java-логику. Из выражения можно вызвать метод бина Spring:
External Worker
Задача External worker создает задание внешнего обработчика Flowable и приостанавливает процесс, пока какой-нибудь обработчик не заберет и не выполнит это задание. Внешний обработчик забирает задания через Java API или REST API, выполняет работу и сообщает результат обратно во Flowable.
Задача внешнего обработчика — это состояние ожидания. Когда обработчик выполнит задание и сообщит об этом во Flowable, процесс продолжится.
В BPMN XML задача внешнего обработчика представлена сервисной задачей с flowable:type="external-worker" и топиком:
<serviceTask id="externalWorkerOrder"
flowable:type="external-worker"
flowable:topic="orderService" />
Повторные попытки при сбое
Как работают повторные попытки, см. в разделе Повторные попытки и сбойные задания.
Настройка
Чтобы задать параметры Fail retry, используйте поле Failed job retry time cycle в разделе Asynchronous execution свойств сервисной задачи. Поле показывается, только если выбрано Asynchronous или Asynchronous leave. Если снять оба флажка, значение поля очищается.
Значение должно быть выражением временного цикла по стандарту ISO 8601.
Пример R5/PT7M выше заставляет исполнитель заданий повторить задание 5 раз с паузой 7 минут между попытками.
XML-представление
Параметр повторных попыток задается элементом flowable:failedJobRetryTimeCycle.
Пример использования:
<serviceTask id="failingServiceTask"
flowable:async="true"
flowable:class="org.flowable.engine.test.jobexecutor.RetryFailingDelegate">
<extensionElements>
<flowable:failedJobRetryTimeCycle>R5/PT7M</flowable:failedJobRetryTimeCycle>
</extensionElements>
</serviceTask>
Инжектирование полей
Инжектирование полей (field injections) — механизм Flowable для передачи параметров в класс Java Delegate в виде фиксированных строковых значений или выражений, которые разрешаются в строки.
Инжектируемое поле всегда должно иметь тип org.flowable.common.engine.api.delegate.Expression.
Когда инжектированное выражение разрешено, его можно привести к нужному целевому типу.
|
Через инжектирование полей нельзя передавать сущности или другие объекты. Фактически выражение разрешается в тип |
Как инжектировать поля:
-
Определите поля в классе-делегате:
private Expression messageField; private Expression quantityField; -
Выберите сервисную задачу и создайте поля с теми же именами, что и в коде. Значениями полей могут быть выражения или строки.
Если нужно передать числовые значения, используйте выражение, как показано выше, например
${3}. Если написать просто 3, это будет интерпретировано как объектString"3", который нельзя привести к типуInteger. -
Во время выполнения движок процессов разрешает выражения и передает получившиеся строки в класс-делегат.
-
В классе Java Delegate должен быть код, который получает значения из полей и приводит их к нужным типам:
String message = (String) messageField.getValue(execution); Integer quantity = (Integer) quantityField.getValue(execution);
Инжектирование полей и потокобезопасность
В целом сервисные задачи с Java-делегатами и инжектированием полей потокобезопасны. Однако есть несколько ситуаций, в которых потокобезопасность не гарантируется: это зависит от настройки или окружения, в котором работает Flowable.
- Тип задачи Java delegate class
-
В этом случае инжектирование полей всегда потокобезопасно. Для каждой сервисной задачи, которая ссылается на определенный класс, создается новый экземпляр, и поля инжектируются один раз при создании экземпляра. Многократное использование одного класса в разных задачах или определениях процессов не создает проблем.
Учтите, что разные экземпляры процесса используют один и тот же экземпляр класса Java Delegate, на который ссылается задача. Можно представить, что один экземпляр процесса влияет на другой, но это очень маловероятно.
- Типы задачи Spring bean и expression
-
Технически для Flowable сервисная задача с бином Spring представлена атрибутом
flowable:expression.При использовании атрибута
flowable:expressionинжектирование полей не нужно. Параметры передаются через вызовы методов, а они всегда потокобезопасны.Строго говоря, инжектирование полей использовать можно, но не нужно.
- Сервисная задача с delegate expression
-
При использовании атрибута
flowable:delegateExpressionпотокобезопасность экземпляра делегата зависит от того, как разрешается выражение. Если выражение-делегат повторно используется в разных задачах или определениях процессов и всегда возвращает один и тот же экземпляр, инжектирование полей не потокобезопасно.Две сервисные задачи могут использовать одно и то же выражение-делегат, но инжектировать разные значения в поле
Expression. Если выражение разрешается в один и тот же экземпляр, при одновременном выполнении процессов возможны состояния гонки при инжектировании поля someField.Проще всего решить эту проблему одним из способов:
-
Переписать Java Delegate так, чтобы использовать выражение или бин Spring и передавать нужные данные через аргументы метода.
-
Возвращать новый экземпляр класса-делегата при каждом разрешении выражения-делегата. Например, при использовании Spring это значит, что для бина нужно задать область prototype (например, добавив к классу-делегату аннотацию
@Scope(SCOPE_PROTOTYPE)).
-
Пример
public class UpperCaseJavaDelegate implements JavaDelegate {
private Expression messageField;
private Expression quantityField;
@Override
public void execute(DelegateExecution execution) {
String message = (String) messageField.getValue(execution);
Integer quantity = (Integer) quantityField.getValue(execution);
String upperCaseMessage = message.toUpperCase();
for (int i = 0; i < quantity; i++) {
System.out.println(upperCaseMessage);
}
}
}