Перейти к основному содержимому

Руководство по Activiti с Java

· 9 мин. чтения

Задача: Сумма двух чисел

Напишите функцию twoSum. Которая получает массив целых чисел nums и целую сумму target, а возвращает индексы двух чисел, сумма которых равна target. Любой набор входных данных имеет ровно одно решение, и вы не можете использовать один и тот же элемент дважды. Ответ можно возвращать в любом порядке...

ANDROMEDA

1. Обзор

Activiti API — это система управления рабочими процессами и бизнес-процессами. Мы можем определить в нем процесс, выполнить его и манипулировать им по-разному, используя сервисы, предоставляемые API. Требуется JDK 7+.

Разработку с использованием API можно вести в любой IDE, но для использования Activiti Designer нам понадобится Eclipse.

Мы можем определить в нем процесс, используя стандарт BPMN 2.0. Есть и другой, менее популярный способ — использование Java-классов вроде StartEvent , EndEvent , UserTask , SequenceFlow и т. д.

Если мы хотим запустить процесс или получить доступ к любой из служб, нам нужно создать ProcessEngineConfiguration .

Мы можем получить ProcessEngine с помощью ProcessEngineConfiguration некоторыми способами, которые мы обсудим далее в этой статье . Через ProcessEngine мы можем выполнять операции Workflow и BPMN .

2. Зависимости Maven

Чтобы использовать этот API, нам нужно включить зависимость Activiti:

<dependency>
<groupId>org.activiti</groupId>
<artifactId>activiti-engine</artifactId>
</dependency>

3. Создание ProcessEngine

ProcessEngine в Activiti обычно настраивается с помощью XML-файла activiti.cfg.xml . Пример этого файла конфигурации:

<beans xmlns="...">
<bean id="processEngineConfiguration" class=
"org.activiti.engine.impl.cfg.StandaloneProcessEngineConfiguration">
<property name="jdbcUrl"
value="jdbc:h2:mem:activiti;DB_CLOSE_DELAY=1000" />
<property name="jdbcDriver" value="org.h2.Driver" />
<property name="jdbcUsername" value="root" />
<property name="jdbcPassword" value="" />
<property name="databaseSchemaUpdate" value="true" />
</bean>
</beans>

Теперь мы можем получить ProcessEngine с помощью класса ProcessEngine :

ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();

Этот оператор будет искать файл activiti.cfg. xml в пути к классам и создайте ProcessEngine на основе конфигурации в файле.

Пример кода для файла конфигурации показывает, что это просто конфигурация на основе Spring. Но это не означает, что мы можем использовать Activiti только в среде Spring. Возможности Spring используются только внутри для создания ProcessEngine .

Давайте напишем тестовый пример JUnit, который создаст ProcessEngine , используя файл конфигурации, показанный выше:

@Test
public void givenXMLConfig_whenGetDefault_thenGotProcessEngine() {
ProcessEngine processEngine
= ProcessEngines.getDefaultProcessEngine();
assertNotNull(processEngine);
assertEquals("root", processEngine.getProcessEngineConfiguration()
.getJdbcUsername());
}

4. API и службы Activiti Process Engine

Точкой входа для взаимодействия с API является ProcessEngine . Через ProcessEngine мы можем получить доступ к различным службам, которые предоставляют методы рабочего процесса/BPMN. ProcessEngine и все сервисные объекты являются потокобезопасными.

./2a8c1445f0c3b345418d83af2a383003.png

Взято с https://www.activiti.org/userguide/images/api.services.png

Класс ProcessEngines просканирует файлы activiti.cfg.xml и activiti-context.xml . Как упоминалось ранее, для всех файлов activiti.cfg.xml ProcessEngine будет создан стандартным образом.

Принимая во внимание, что для всех файлов activiti-context.xml он будет создан способом Spring — я создам контекст приложения Spring и получу из него ProcessEngine . Во время выполнения процесса все шаги будут посещены в порядке, определенном в файле BPMN.

Во время выполнения процесса все шаги будут посещены в порядке, определенном в файле BPMN.

4.1. Определение процесса и родственные термины

ProcessDefinition представляет бизнес-процесс. Он используется для определения структуры и поведения различных этапов процесса. Развертывание определения процесса означает загрузку определения процесса в базу данных Activiti.

Определения процессов в основном определяются стандартом BPMN 2.0. Их также можно определить с помощью кода Java. Все термины, определенные в этом разделе, также доступны в виде классов Java.

Как только мы начинаем запускать определение процесса, его можно назвать процессом.

ProcessInstance — это одно выполнение ProcessDefinition .

StartEvent связан с каждым бизнес-процессом. Он указывает точку входа в процесс. Точно так же существует EndEvent , указывающий на конец процесса. Мы можем определить условия для этих событий.

Все шаги (или элементы) между началом и концом называются задачами . Задания могут быть разных типов. Наиболее часто используемые задачи — UserTasks и ServiceTasks .

UserTasks , как следует из названия, таковы, что пользователь должен выполнять их вручную.

`С другой стороны, ServiceTasks настраиваются с помощью фрагмента кода .` Всякий раз, когда выполнение достигает их, их блок кода будет выполнен.

SequenceFlows соединяют Задачи . Мы можем определить SequenceFlows по исходным и целевым элементам, которые они будут соединять. Опять же, мы также можем определить условия для SequenceFlows , чтобы создать условные пути в процессе.

4.2. Услуги

Кратко обсудим услуги, предоставляемые Activiti:

  • RepositoryService помогает нам манипулировать развертыванием определений процессов. Эта служба работает со статическими данными, связанными с определением процесса.
  • RuntimeService управляет ProcessInstances (текущими запущенными процессами), а также переменными процесса.
  • TaskService отслеживает UserTasks . Задачи , которые пользователь должен выполнять вручную, лежат в основе API Activiti. С помощью этого сервиса мы можем создать задачу, потребовать и выполнить задачу, манипулировать исполнителем задачи и т. д.
  • FormService является необязательным сервисом. API можно использовать без него и без ущерба для каких-либо его функций. Он используется для определения начальной формы и формы задачи в процессе.
  • IdentityService управляет пользователями и группами
  • HistoryService отслеживает историю Activiti Engine. Мы также можем установить различные уровни истории.
  • ManagementService связан с метаданными и обычно не требуется при создании приложения.
  • DynamicBpmnService помогает нам что-либо изменить в процессе без его

5. Работа с сервисами Activiti

Чтобы узнать, как мы можем работать с различными сервисами и запускать процесс, давайте возьмем пример процесса «Заявка на отпуск сотрудника»:

./c154ef333806de4f69613d4bb1c8c796.png

Файл BPMN 2.0 VacationRequest.bpmn20.xml для этого процесса будет иметь начальное событие, определенное как:

<startEvent id="startEvent" name="request" 
activiti:initiator="employeeName">
<extensionElements>
<activiti:formProperty id="numberOfDays"
name="Number of days" type="long" required="true"/>
<activiti:formProperty id="startDate"
name="Vacation start date (MM-dd-yyyy)" type="date"
datePattern="MM-dd-yyyy hh:mm" required="true"/>
<activiti:formProperty id="reason" name="Reason for leave"
type="string"/>
</extensionElements>
</startEvent>

Аналогично, первая пользовательская задача, назначенная группе пользователей «управление», будет выглядеть так:

<userTask id="handle_vacation_request" name=
"Handle Request for Vacation">
<documentation>${employeeName} would like to take ${numberOfDays} day(s)
of vacation (Motivation: ${reason}).</documentation>
<extensionElements>
<activiti:formProperty id="vacationApproved" name="Do you approve
this vacation request?" type="enum" required="true"/>
<activiti:formProperty id="comments" name="Comments from Manager"
type="string"/>
</extensionElements>
<potentialOwner>
<resourceAssignmentExpression>
<formalExpression>management</formalExpression>
</resourceAssignmentExpression>
</potentialOwner>
</userTask>

С помощью ServiceTask нам нужно определить фрагмент кода, который необходимо выполнить. У нас есть этот фрагмент кода как класс Java:

<serviceTask id="send-email-confirmation" name="Send email confirmation" 
activiti:class=
"com.example.activiti.servicetasks.SendEmailServiceTask.java">
</serviceTask>

Условный поток будет показан путем добавления тега «conditionExpression» в «sequenceFlow»:

<sequenceFlow id="flow3" name="approved" 
sourceRef="sid-12A577AE-5227-4918-8DE1-DC077D70967C"
targetRef="send-email-confirmation">
<conditionExpression xsi:type="tFormalExpression">
<![CDATA[${vacationApproved == 'true'}]]>
</conditionExpression>
</sequenceFlow>

Здесь VacationApproved — это formProperty пользовательской задачи, показанной выше.

Как мы видим на диаграмме, это очень простой процесс. Сотрудник составляет заявление на отпуск, указывая количество дней и дату начала отпуска. Заявка поступает к менеджеру. Они могут одобрить/отклонить запрос.

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

Служебным задачам предоставляется некоторый фрагмент кода для выполнения (здесь в виде класса Java). Мы дали класс SendEmailServiceTask.java.

Эти типы классов должны расширять JavaDelegate. Также нам нужно переопределить его метод execute() , который будет выполняться, когда выполнение процесса достигнет этого шага.

5.1. Развертывание процесса

Чтобы сделать наш процесс известным для Activiti Engine, нам нужно развернуть процесс. Мы можем сделать это программно, используя RepositoryService. Давайте напишем тест JUnit, чтобы показать это:

@Test 
public void givenBPMN_whenDeployProcess_thenDeployed() {
ProcessEngine processEngine
= ProcessEngines.getDefaultProcessEngine();
RepositoryService repositoryService
= processEngine.getRepositoryService();
repositoryService.createDeployment()
.addClasspathResource(
"org/activiti/test/vacationRequest.bpmn20.xml")
.deploy();
Long count=repositoryService.createProcessDefinitionQuery().count();
assertEquals("1", count.toString());
}

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

Следовательно, впоследствии мы можем запросить службу репозитория , чтобы получить развернутые процессы; определения процесса .

5.2. Запуск ProcessInstance

После развертывания ProcessDefinition в Activiti Engine мы можем выполнить процесс, создав ProcessInstances . ProcessDefinition — это план, а ProcessInstance — его выполнение во время выполнения.

Для одного ProcessDefinition может быть несколько ProcessInstances .

Доступ ко всем деталям, связанным с ProcessInstances , можно получить через RuntimeService .

В нашем примере в стартовом событии нужно передать количество дней отпуска, дату начала и причину. Мы будем использовать переменные процесса и передавать их при создании ProcessInstance.

Давайте напишем тестовый пример JUnit, чтобы лучше понять:

@Test
public void givenDeployedProcess_whenStartProcessInstance_thenRunning() {
//deploy the process definition
Map<String, Object> variables = new HashMap>();
variables.put("employeeName", "John");
variables.put("numberOfDays", 4);
variables.put("vacationMotivation", "I need a break!");

RuntimeService runtimeService = processEngine.getRuntimeService();
ProcessInstance processInstance = runtimeService
.startProcessInstanceByKey("vacationRequest", variables);
Long count=runtimeService.createProcessInstanceQuery().count();

assertEquals("1", count.toString());
}

Несколько экземпляров одного определения процесса будут отличаться переменными процесса.

Существует несколько способов запустить экземпляр процесса. Здесь мы используем ключ процесса. После запуска экземпляра процесса мы можем получить информацию о нем, запросив RuntimeService .

5.3. Выполнение задач

Когда наш экземпляр процесса запускается, первым шагом является пользовательская задача, назначенная группе пользователей «управление».

У пользователя может быть почтовый ящик со списком задач, которые он должен выполнить. Теперь, если мы хотим продолжить выполнение процесса, пользователю необходимо завершить эту задачу. Для Activiti Engine это называется «выполнение задачи».

Мы можем запросить TaskService, чтобы получить объект задачи, а затем выполнить ее.

Код, который нам нужно написать для этого, выглядит так:

@Test 
public void givenProcessInstance_whenCompleteTask_thenGotNextTask() {
// deploy process and start process instance
TaskService taskService = processEngine.getTaskService();
List<Task> tasks = taskService.createTaskQuery()
.taskCandidateGroup("management").list();
Task task = tasks.get(0);

Map<String, Object> taskVariables = new HashMap<>();
taskVariables.put("vacationApproved", "false");
taskVariables.put("comments", "We have a tight deadline!");
taskService.complete(task.getId(), taskVariables);

Task currentTask = taskService.createTaskQuery()
.taskName("Modify vacation request").singleResult();
assertNotNull(currentTask);
}

Обратите внимание, что метод Complete () службы TaskService также принимает необходимые переменные процесса. Передаем ответ от менеджера.

После этого процессный движок перейдет к следующему шагу. Здесь следующий шаг спрашивает сотрудника, следует ли повторно отправить запрос на отпуск или нет.

Итак, наш ProcessInstance теперь ожидает на этом UserTask, который имеет название «Изменить запрос на отпуск».

5.4. Приостановка и активация процесса

Мы можем приостановить ProcessDefinition , а также ProcessInstance . Если мы приостановим ProcessDefinition, мы не сможем создать его экземпляр, пока он приостановлен. Мы можем сделать это с помощью RepositoryService:

@Test(expected = ActivitiException.class)
public void givenDeployedProcess_whenSuspend_thenNoProcessInstance() {
// deploy the process definition
repositoryService.suspendProcessDefinitionByKey("vacationRequest");
runtimeService.startProcessInstanceByKey("vacationRequest");
}

Чтобы активировать его снова, нам просто нужно вызвать один из методов репозиторияService.activateProcessDefinitionXXX .

Точно так же мы можем приостановить ProcessInstance, используя RuntimeService.

6. Заключение

В этой статье мы увидели, как можно использовать Activiti с Java. Мы создали образец файла ProcessEngineCofiguration , который помогает нам создать ProcessEngine .

С его помощью мы получили доступ к различным сервисам, предоставляемым API. Эти сервисы помогают нам управлять и отслеживать ProcessDefinitions , ProcessInstances , UserTasks и т. д.

Как всегда, код примеров, которые мы видели в статье, лежит на GitHub .