Process Engine API

Этот раздел перенесён из документации Camunda 7 и в дальнейшем будет доработан с учётом особенностей OpenBPM Engine

API сервисов (Services API)

Java API — наиболее распространённый способ взаимодействия с движком. Центральной точкой входа является ProcessEngine, который может быть создан несколькими способами, как описано в разделе конфигурации. Из ProcessEngine можно получить различные сервисы, содержащие методы workflow/BPM. ProcessEngine и объекты сервисов являются потокобезопасными. Поэтому вы можете хранить ссылку на один из них на протяжении всего времени работы сервера.

API Services

ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();

RepositoryService repositoryService = processEngine.getRepositoryService();
RuntimeService runtimeService = processEngine.getRuntimeService();
TaskService taskService = processEngine.getTaskService();
IdentityService identityService = processEngine.getIdentityService();
FormService formService = processEngine.getFormService();
HistoryService historyService = processEngine.getHistoryService();
ManagementService managementService = processEngine.getManagementService();
FilterService filterService = processEngine.getFilterService();
ExternalTaskService externalTaskService = processEngine.getExternalTaskService();
CaseService caseService = processEngine.getCaseService();
DecisionService decisionService = processEngine.getDecisionService();

ProcessEngines.getDefaultProcessEngine() при первом вызове инициализирует и собирает (build) процессный движок, а затем всегда возвращает тот же самый экземпляр. Корректное создание и закрытие всех процессных движков можно выполнить с помощью ProcessEngines.init() и ProcessEngines.destroy().

Класс ProcessEngines выполняет поиск всех файлов camunda.cfg.xml и activiti.cfg.xml. Для всех файлов camunda.cfg.xml процессный движок будет создан типичным способом:

ProcessEngineConfiguration
  .createProcessEngineConfigurationFromInputStream(inputStream)
  .buildProcessEngine()

Для всех файлов activiti.cfg.xml процессный движок будет создан «по Spring-способу»: сначала создаётся контекст Spring-приложения, а затем процессный движок извлекается из этого контекста.

Все сервисы не имеют состояния (stateless). Это означает, что вы можете легко запускать OpenBPM Engine на нескольких узлах в кластере, где все узлы работают с одной и той же базой данных, не беспокоясь о том, какая машина фактически выполняла предыдущие вызовы. Любой вызов любого сервиса является идемпотентным независимо от того, где он выполняется.

RepositoryService — вероятно, первый сервис, который потребуется при работе с движком OpenBPM Engine. Этот сервис предоставляет операции для управления и манипулирования деплоями (deployments) и определениями процессов (process definitions). Не углубляясь здесь в детали: определение процесса — это Java-аналог BPMN 2.0 процесса. Это представление структуры и поведения каждого шага процесса. Деплой — единица упаковки внутри движка. Деплой может содержать несколько BPMN 2.0 XML-файлов и любые другие ресурсы. Что именно включать в один деплой — решает разработчик. Это может варьироваться от одного BPMN 2.0 XML-файла процесса до целого пакета процессов и связанных ресурсов (например, деплой hr-processes может содержать всё, что относится к HR-процессам). RepositoryService позволяет деплоить такие пакеты. Деплой деплоя означает, что он загружается в движок, где все процессы проверяются и парсятся перед сохранением в базе данных. С этого момента деплой известен системе, и любой процесс, включённый в деплой, теперь может быть запущен.

Кроме того, этот сервис позволяет:

  • Выполнять запросы (query) по деплоям и определениям процессов, известным движку.

  • Приостанавливать и активировать определения процессов. Приостановка означает, что над ними нельзя выполнять дальнейшие операции; активация — обратная операция.

  • Получать различные ресурсы, такие как файлы, содержащиеся в деплое, или диаграммы процессов, автоматически сгенерированные движком.

Если RepositoryService относится к статической информации (т. е. данным, которые не меняются или меняются незначительно), то RuntimeService — почти полная противоположность. Он занимается запуском новых экземпляров процессов (process instances) по определениям процессов. Как упоминалось выше, определение процесса задаёт структуру и поведение различных шагов процесса. Экземпляр процесса — это одно выполнение такого определения процесса. Для каждого определения процесса обычно одновременно выполняется много экземпляров. RuntimeService также является сервисом, который используется для получения и сохранения переменных процесса. Это данные, специфичные для конкретного экземпляра процесса, и они могут использоваться различными конструкциями в процессе (например, исключающий шлюз часто использует переменные процесса, чтобы определить, по какому пути продолжить выполнение процесса). RuntimeService также позволяет выполнять запросы по экземплярам процессов и выполнениям (executions). Execution — представление BPMN 2.0 концепции «токена». По сути execution — это указатель на то, где сейчас находится экземпляр процесса. Наконец, RuntimeService используется всякий раз, когда экземпляр процесса ожидает внешний триггер и процесс необходимо продолжить. Экземпляр процесса может иметь различные состояния ожидания, и этот сервис содержит различные операции, чтобы «сигнализировать» экземпляру, что внешний триггер получен и выполнение процесса можно продолжить.

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

  • Запросы задач, назначенных пользователям или группам.

  • Создание новых автономных задач (standalone tasks). Это задачи, не связанные с экземплярами процессов.

  • Управление тем, какому пользователю назначена задача, или какими пользователями задача так или иначе затрагивается/с ними связана.

  • Захват (claim) и завершение (complete) задачи. Claim означает, что кто-то решил стать исполнителем (assignee) задачи, то есть этот пользователь будет её выполнять. Complete означает «выполнить работу по задаче». Обычно это заполнение некоторой формы.

IdentityService довольно прост. Он позволяет управлять (создание, обновление, удаление, запросы, …​) группами и пользователями. Важно понимать, что ядро движка фактически не выполняет никаких проверок пользователей во время выполнения. Например, задача может быть назначена любому пользователю, но движок не проверяет, известен ли этот пользователь системе. Это связано с тем, что движок может использоваться совместно с такими сервисами, как LDAP, Active Directory и т. п.

FormService — необязательный сервис. Это означает, что движок OpenBPM Engine можно прекрасно использовать и без него, не жертвуя функциональностью. Этот сервис вводит понятие стартовой формы (start form) и формы задачи (task form). Стартовая форма — форма, которая показывается пользователю до запуска экземпляра процесса, а форма задачи — форма, которая отображается, когда пользователь хочет завершить задачу. Вы можете определить эти формы в BPMN 2.0 определении процесса. Этот сервис предоставляет эти данные в удобном для работы виде. Но опять же, это опционально, так как формы не обязательно должны быть встроены в определение процесса.

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

ManagementService обычно не требуется при разработке пользовательских приложений. Он позволяет получать информацию о таблицах базы данных и их метаданных. Кроме того, он предоставляет возможности запросов и операции управления для jobs. Jobs используются в движке для различных целей, таких как таймеры, асинхронные продолжения, отложенная приостановка/активация и т. п. Позже эти темы будут рассмотрены подробнее.

FilterService позволяет создавать и управлять фильтрами. Фильтры — это сохранённые запросы, например запросы задач. Так, фильтры используются в Tasklist, чтобы фильтровать пользовательские задачи.

ExternalTaskService предоставляет доступ к экземплярам внешних задач link:{{< relref "external-tasks.md" >}}[]. Внешние задачи представляют собой элементы работы, которые обрабатываются внешне и независимо от процессного движка.

CaseService похож на RuntimeService, но для экземпляров кейсов (case instances). Он занимается запуском новых экземпляров кейсов по определениям кейсов и управлением жизненным циклом выполнений кейса (case executions). Сервис также используется для получения и обновления переменных процесса экземпляров кейсов.

DecisionService позволяет выполнять оценку решений (decisions), задеплоенных в движок. Это альтернатива вычислению решения внутри задачи бизнес-правил (business rule task), независимая от определения процесса.

Для более подробной информации об операциях сервисов и API движка см. {{< javadocref page="" text="Java Docs" >}}.

Query API

Для выполнения запросов к данным в движке есть несколько вариантов:

  • Java Query API: Fluent Java API для запросов к сущностям движка (например, ProcessInstances, Tasks, …​).

  • REST Query API: REST API для запросов к сущностям движка (например, ProcessInstances, Tasks, …​).

  • Native Queries: возможность задавать собственные SQL-запросы для получения сущностей движка (например, ProcessInstances, Tasks, …​), если возможностей Query API недостаточно (например, для OR-условий).

  • Custom Queries: использование полностью кастомных запросов и собственного маппинга MyBatis для получения собственных value objects или для объединения данных движка с доменными данными.

  • SQL Queries: использование SQL-запросов базы данных для сценариев вроде отчётности (Reporting).

Рекомендуемый способ — использовать один из Query API.

Java Query API позволяет программировать полностью типобезопасные (typesafe) запросы с fluent API. Вы можете добавлять различные условия к запросам (все они применяются вместе как логическое AND) и ровно одну сортировку. Следующий код показывает пример:

List<Task> tasks = taskService.createTaskQuery()
  .taskAssignee("kermit")
  .processVariableValueEquals("orderId", "0815")
  .orderByDueDate().asc()
  .list();

Дополнительную информацию можно найти в {{< javadocref page="" text="Java Docs" >}}.

Ограничение максимального числа результатов запроса (Query Maximum Results Limit)

Запрос результатов без ограничения максимального числа результатов или запрос очень большого количества результатов может привести к высокому потреблению памяти или даже к исключениям out of memory. С помощью Query Maximum Results Limit можно ограничить максимальное число результатов.

Это ограничение применяется только в следующих случаях:

  • аутентифицированный пользователь выполняет запрос

  • Query API вызывается напрямую, например через REST API (ограничение не применяется внутри процесса через Delegation Code)

Запрещено (Forbidden)

  • Выполнение запроса с неограниченным числом результатов с использованием метода #list()

  • Выполнение постраничного запроса #_paginated_queries с превышением настроенного лимита максимального числа результатов

  • Выполнение синхронной операции на основе запроса, затрагивающей больше экземпляров, чем лимит максимального числа результатов (вместо этого используйте Batch Operation)

Разрешено (Allowed)

  • Выполнение запроса с использованием метода [Query#unlimitedList][javadocs-query-unlimited-list]

  • Выполнение постраничного запроса #_paginated_queries с максимальным числом результатов меньше либо равным лимиту максимального числа результатов

  • Выполнение Native Query #_native_queries поскольку он недоступен через REST API или Webapps и, следовательно, вряд ли может быть использован во вред

Ограничения (Limitations)

  • Выполнение статистического запроса через REST API

  • Выполнение запроса вызываемого экземпляра (called instance query) через Webapps (private API)

Пользовательские запросы Identity Service (Custom Identity Service Queries)

Когда вы предоставляете…​

  1. пользовательскую реализацию identity provider, реализуя интерфейс ReadOnlyIdentityProvider или WritableIdentityProvider

  2. И отдельную реализацию запросов Identity Service (например, GroupQuery, TenantQuery, UserQuery)

Убедитесь, что при вызове [Query#unlimitedList][javadocs-query-unlimited-list] возвращаются все результаты без каких-либо ограничений. Возможность получить неограниченный список важна, чтобы REST API работал корректно, поскольку некоторые endpoints полагаются на получение неограниченных результатов.

[javadocs-query-unlimited-list]: {{< javadocref_url page="org/camunda/bpm/engine/query/Query.html#unlimitedList--" >}}

Постраничные запросы (Paginated Queries)

Пагинация позволяет настроить максимальное число результатов, возвращаемых запросом, а также позицию (индекс) первого результата.

См. следующий пример:

List<Task> tasks = taskService.createTaskQuery()
  .taskAssignee("kermit")
  .processVariableValueEquals("orderId", "0815")
  .orderByDueDate().asc()
  .listPage(20, 50);

Запрос, показанный выше, получает 50 результатов, начиная с результата с индексом 20.

OR-запросы (OR Queries)

Поведение Query API по умолчанию связывает критерии фильтрации выражением AND. OR-запросы позволяют строить запросы, в которых критерии фильтрации связываются выражением OR.

[NOTE, caption=Осторожно!]: - Эта функциональность доступна только для запросов задач и экземпляров процессов (runtime & history). - Следующие методы нельзя применять к OR-запросу: orderBy…​(), initializeFormKeys(), withCandidateGroups(), withoutCandidateGroups(), withCandidateUsers(), withoutCandidateUsers(), incidentIdIn().

После вызова or() может следовать цепочка из нескольких критериев фильтрации. Каждый критерий фильтрации связывается выражением OR. Вызов endOr() обозначает конец OR-запроса. Вызов этих двух методов сопоставим с заключением критериев фильтрации в скобки.

List<Task> tasks = taskService.createTaskQuery()
  .taskAssignee("John Munda")
  .or()
    .taskName("Approve Invoice")
    .taskPriority(5)
  .endOr()
  .list();

Запрос выше получает все задачи, которые назначены "John Munda" и одновременно либо имеют имя "Approve Invoice", либо имеют пятый уровень приоритета (assignee = "John Munda" AND (name = "Approve Invoice" OR priority = 5), <a href="https://en.wikipedia.org/wiki/Conjunctive_normal_form">Conjunctive Normal Form</a>).

Внутри движка запрос транслируется в следующий SQL-запрос (слегка упрощённый):

SELECT DISTINCT *
FROM   act_ru_task RES
WHERE  RES.assignee_ = 'John Munda'
       AND ( Upper(RES.name_) = Upper('Approve Invoice')
             OR RES.priority_ = 5 );

Одновременно можно использовать произвольное количество OR-запросов. При построении запроса, который состоит не только из одного OR-запроса, но также включает критерии фильтрации, связанные выражением AND, OR-запрос добавляется в цепочку критериев через ведущий оператор AND.

Критерий фильтрации, связанный с переменными, можно применять несколько раз в рамках одного OR-запроса:

List<Task> tasks = taskService.createTaskQuery()
  .or()
    .processVariableValueEquals("orderId", "0815")
    .processVariableValueEquals("orderId", "4711")
    .processVariableValueEquals("orderId", "4712")
  .endOr()
  .list();

Помимо критериев фильтрации, связанных с переменными, поведение отличается. Каждый раз, когда критерий фильтрации, не связанный с переменными используется более одного раза внутри запроса, учитывается только значение, применённое последним:

List<Task> tasks = taskService.createTaskQuery()
  .or()
    .taskCandidateGroup("sales")
    .taskCandidateGroup("controlling")
  .endOr()
  .list();

[NOTE, caption=Осторожно!]: В запросе, показанном выше, значение "sales" критерия фильтрации taskCandidateGroup заменяется значением "controlling". Чтобы избежать такого поведения, можно использовать критерии фильтрации с суффиксом …​In, например:

  • taskCandidateGroupIn()

  • tenantIdIn()

  • processDefinitionKeyIn()

REST Query API

Java Query API также доступен как REST-сервис; подробности см. в REST documentation.

Native Queries

Иногда требуются более мощные запросы, например запросы с оператором OR или ограничения, которые невозможно выразить с помощью Query API. Для таких случаев были введены native queries, которые позволяют писать собственные SQL-запросы. Тип возвращаемого значения определяется объектом Query, который вы используете, а данные маппятся в корректные объекты, например Task, ProcessInstance, Execution и т. д. Поскольку запрос будет выполняться на стороне базы данных, необходимо использовать имена таблиц и колонок так, как они определены в database schema. Это требует некоторого знания внутренней структуры данных, и рекомендуется использовать native queries с осторожностью. Имена таблиц можно получать через API, чтобы сделать зависимость как можно меньше.

List<Task> tasks = taskService.createNativeTaskQuery()
  .sql("SELECT * FROM " + managementService.getTableName(Task.class) + " T WHERE T.NAME_ = #{taskName}")
  .parameter("taskName", "aOpenTask")
  .list();

long count = taskService.createNativeTaskQuery()
  .sql("SELECT count(*) FROM " + managementService.getTableName(Task.class) + " T1, "
         + managementService.getTableName(VariableInstanceEntity.class) + " V1 WHERE V1.TASK_ID_ = T1.ID_")
  .count();

Custom Queries

Из соображений производительности иногда может быть предпочтительно запрашивать не объекты движка, а собственные value-объекты или DTO, собирающие данные из разных таблиц — возможно, включая ваши доменные классы.

Оптимизация производительности с помощью пользовательских запросов http://blog.camunda.org/post/2017/12/custom-queries.

SQL Queries

Структура таблиц достаточно прямолинейна — мы сосредоточились на том, чтобы сделать её понятной. Поэтому допустимо выполнять SQL-запросы, например для задач отчётности. Просто убедитесь, что вы не нарушите данные движка, обновляя таблицы, не понимая точно, что вы делаете.

Лицензия и атрибуция

Эта документация была создана на базе материала "Camunda 7 Docs" от Camunda, находится под лицензией Creative Commons Attribution-ShareAlike 3.0 Unported License .

Оригинал документации: https://docs.camunda.org