Сервисная задача

Обзор

Сервисная задача используется для вызова Java-логики, внешних сервисов, API или автоматизированных приложений.

Графическая нотация

Сервисная задача изображается как прямоугольник со скругленными углами и небольшим значком шестеренки в левом верхнем углу.

service task
XML-представление

В зависимости от реализации XML-представление сервисной задачи может различаться. Примеры см. в описаниях соответствующих типов задачи.

Свойства

В моделере BPMN можно задать следующие свойства сервисной задачи:

service task properties

Свойство

Описание

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

  • Asynchronous

  • Exclusive

  • Asynchronous leave

  • Asynchronous leave exclusive

Обновлено в 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. В нем можно указать одну из существующих переменных процесса или создать новую, просто введя ее имя.

Следите за типами при использовании существующих переменных. Если тип результата отличается от типа существующей переменной, создается новая переменная процесса с тем же именем. Если была переменная a1 типа String, а в нее сохраняется числовой результат 100L, появится новая переменная a1 типа Long со значением 100.

Рядом с Result variable находится флажок Use local scope. Если флажок установлен, переменная результата, созданная сервисной задачей, получает локальную область в контексте исполнения задачи. Это значит, что переменная доступна только в текущем исполнении и не передается в родительское исполнение или экземпляр процесса.

Этот параметр помогает изолировать переменную в конкретном исполнении сервисной задачи. Если одновременно выполняется несколько экземпляров одной сервисной задачи, у каждого экземпляра будет своя локальная переменная, и они не будут мешать друг другу.

XML-представление

Вот как все параметры сервисной задачи с бином Spring представлены в XML:

<serviceTask id="set-status-service-task" name="Set order status"
    flowable:async="true" (1)
    flowable:expression="${smpl_OrderStatusBean.setStatus(orderId,&#39;Sent&#39;)}" (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

Затем укажите этот метод в сервисной задаче:

execution as parameter

Java Delegate Class

В этом случае бизнес-логику выполняет класс, который реализует интерфейс org.flowable.engine.delegate.JavaDelegate или org.flowable.engine.delegate.FutureJavaDelegate и переопределяет его метод execute(). Метод получает объект execution как параметр, поэтому у вас есть доступ к контексту процесса, включая все переменные процесса.

Если выбрать вариант JavaDelegate class в поле Task type, можно выбрать подходящий класс:

create java delegate

Например, реализуем класс, который создает переменную процесса со случайным значением:

public class RandomIndexJavaDelegate implements JavaDelegate {
    @Override
    public void execute(DelegateExecution execution) {
        long randomIndex = new Random().nextLong(100L);
        execution.setVariable("randomIndex", randomIndex);
    }
}
XML-представление

Чтобы указать класс, вызываемый во время выполнения процесса, нужно задать полное имя класса в атрибуте 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, для каждой из них создается отдельный экземпляр. Все экземпляры процесса используют общий экземпляр класса для задачи.

java delegate instantiating

Поэтому класс не должен использовать поля экземпляра и должен быть потокобезопасным, так как он может выполняться одновременно из разных потоков. Это также может влиять на инжектирование полей.

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. Например:

delegate expression
XML-представление

Чтобы указать класс, вызываемый во время выполнения процесса, можно использовать выражение, которое возвращает объект. Для этого в XML используется атрибут flowable:delegateExpression:

<serviceTask id="delegate-expression"
    name="Delegate expression task"
    flowable:delegateExpression="${MyDelegateExpression}"
    jmix:taskType="delegateExpression">
</serviceTask>

Expression

Выражение — самый общий способ вызвать Java-логику. Из выражения можно вызвать метод бина Spring:

expression service task

Вычисление значения выражения

В сервисной задаче можно использовать и выражение-значение. Затем укажите переменную результата, и ей будет присвоен результат выражения. Например:

${'Hello, World!'}

Переменная результата greeting будет равна "Hello, World!".

External Worker

Задача External worker создает задание внешнего обработчика Flowable и приостанавливает процесс, пока какой-нибудь обработчик не заберет и не выполнит это задание. Внешний обработчик забирает задания через Java API или REST API, выполняет работу и сообщает результат обратно во Flowable.

Задача внешнего обработчика — это состояние ожидания. Когда обработчик выполнит задание и сообщит об этом во Flowable, процесс продолжится.

XML-представление

В 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. Если снять оба флажка, значение поля очищается.

fail retry property

Значение должно быть выражением временного цикла по стандарту 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. Когда инжектированное выражение разрешено, его можно привести к нужному целевому типу.

Через инжектирование полей нельзя передавать сущности или другие объекты. Фактически выражение разрешается в тип String. Если строковое значение можно привести к вашему типу, все в порядке. Иначе возникнет ошибка.

Как инжектировать поля:

  1. Определите поля в классе-делегате:

    private Expression messageField;
    private Expression quantityField;
  2. Выберите сервисную задачу и создайте поля с теми же именами, что и в коде. Значениями полей могут быть выражения или строки.

    field injection properties

    Если нужно передать числовые значения, используйте выражение, как показано выше, например ${3}. Если написать просто 3, это будет интерпретировано как объект String "3", который нельзя привести к типу Integer.

  3. Во время выполнения движок процессов разрешает выражения и передает получившиеся строки в класс-делегат.

  4. В классе 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)).

Аннотация @Scope(SCOPE_PROTOTYPE) в Spring задает область бина: новый экземпляр бина создается при каждом запросе к контейнеру Spring. В отличие от этого, при области singleton создается только один экземпляр бина, общий для всего приложения. Область 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);
        }
    }
}