BPMN (англ. Business Process Modeling Notation, «нотация и модель бизнес-процессов») — это язык визуального моделирования бизнес-процессов, использующий графические блок-схемы. Это открытый стандарт, созданный консорциумом Object Management Group (OMG).
Основными целями BPMN являются:
Достижение большей гибкости бизнеса.
Достижение более высоких уровней эффективности и результативности.
Повышение производительности с точки зрения качества, затрат и сроков.
Object Management Group определила ряд стандартов и нотаций, известных как BPM (англ. “Business Process Management”, «управление бизнес-процессами»).
Процессный движок Flowable позволяет разворачивать процессы в соответствии с международным отраслевым стандартом BPMN 2.0. Каждый процесс BPM представляет собой последовательность объектов, связанных с действиями и имеющих стартовое и конечное события.
BPMN используется для автоматизации бизнеса — например, в управлении пользовательским/клиентским опытом или управлении мероприятиями. Он упрощает и ускоряет разработку, уменьшая количество ошибок.
Стартовое событие (Start Event) — объект BPMN-потока, который определяет начальную точку процесса.
Поток операций (Sequence Flow) — объекты потока операций используются для соединения объектов потока. Они играют роль транспортировщика данных между событиями потока.
Шлюз «ИЛИ/ИЛИ» (исключающий, Exclusive Gateway) — основан на условиях и разбивает поток на один или несколько.
Пользовательская задача (User Task) — используется для моделирования задачи, которая должна выполняться человеком. Например, Admin Maker — это пользовательская задача, которая находится под контролем любого пользователя.
Конечное событие (End Event) — это объект BPMN-потока, который определяет точку окончания процесса.
Теперь рассмотрим работу приложения на Spring Boot, которое использует внутренние сервисы с движками бизнес-процессов.
Прежде чем погрузиться в детали Flowable, давайте обсудим, какие сценарии могут использоваться для оптимизации процесса разработки и повышения его эффективности, надёжности и точности.
Приложения для управления бизнес-процессами могут охватывать различные области, такие как системы управления аэропортом или банковские приложения.
Процесс на диаграмме
Давайте рассмотрим процесс на диаграмме. В нём участвуют два участника: создатель / maker (инициатор запроса) и проверяющий / checker (тот, кто подтверждает запрос). Создатель инициирует запрос, а проверяющий должен подтвердить его для завершения процесса.
Начнём с кода:
POM:
Файл pom.xml, представленный ниже, содержит зависимости, используемые для реализации BPMN.
<dependency>
<groupId>org.flowable</groupId>
<artifactId>flowable5-spring-compatibility</artifactId>
<version>${flowable.version}</version>
</dependency>
В процессном движке Flowable:
Элемент
<definations>
является корневым элементом файла определения процесса. Он используется для определения набора процессов, диаграмм взаимодействия и других артефактов, которые являются частью бизнес-процесса.Элемент
<process>
определяет сам бизнес-процесс и содержит начальные и конечные события, а также другие элементы, такие как<userTask>
,<serviceTask>
,<sequenceFlow>
и<exclusiveGateway>
.Элемент
<startEvent>
обозначает начало процесса или подпроцесса.<userTask>
представляет работу, выполняемую пользователем.<sequenceFlow>
обозначает поток управления между действиями в процессе или подпроцессе.exclusiveGateway
— тип шлюза, который используется для обеспечения условного разветвления в процессе или подпроцессе. Он имеет один вход и несколько выходов, и принимает решение о том, какой исходящий поток выбрать на основе заданного условия. Каждый исходящий поток обычно ассоциируется с выражением условия, и шлюз выбирает первый поток, для которого условие оценивается как истинное (true). Если ни одно из условий не является истинным, выбирается поток по умолчанию.endEvent
— это элемент, обозначающий завершение конец процесса или подпроцесса. Его цель — указать конец выполнения процесса и определить действия, которые должны быть выполнены при завершении процесса или подпроцесса.conditionExpression
— это булево выражение, которое используется для определения того, следует ли выполнять последовательность действий или нет. Обычно оно используется в сочетании с исключающим (exclusive) шлюзом, который позволяет процессу разветвляться в разных направлениях на основе оценки условия.
<?xml version="1.0" encoding="UTF-8"?>
<definitions
xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:activiti="http://activiti.org/bpmn"
xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI"
xmlns:omgdc="http://www.omg.org/spec/DD/20100524/DC"
xmlns:omgdi="http://www.omg.org/spec/DD/20100524/DI"
typeLanguage="http://www.w3.org/2001/XMLSchema"
expressionLanguage="http://www.w3.org/1999/XPath"
targetNamespace="http://www.activiti.org/test">
<process id="FlowableProcess" name="FlowableProcess" isExecutable="true">
<startEvent id="startevent1" name="Start"></startEvent>
<userTask id="maker" name="maker" activiti:owner="maker"></userTask>
<userTask id="checker" name="checker" activiti:owner="checker"></userTask>
<sequenceFlow id="Flow_StartToMaker"
sourceRef="startevent1" targetRef="maker"></sequenceFlow>
<exclusiveGateway id="MakerExclusivegateway"
name="Exclusive Gateway Maer"></exclusiveGateway>
<sequenceFlow id="Flow_MakerToGateway" sourceRef="maker"
targetRef="MakerExclusivegateway"></sequenceFlow>
<sequenceFlow id="Flow_MakerGatewayToChecker"
sourceRef="MakerExclusivegateway" targetRef="checker">
<conditionExpression xsi:type="tFormalExpression"><![CDATA[${makerApproved}]]></conditionExpression>
</sequenceFlow>
<exclusiveGateway id="CheckerExclusivegateway"
name="Exclusive Gateway"></exclusiveGateway>
<sequenceFlow id="Flow_CheckerToGateway"
sourceRef="checker" targetRef="CheckerExclusivegateway"></sequenceFlow>
<endEvent id="endevent" name="End"></endEvent>
<sequenceFlow id="Flow_CheckerGatewayToEnd"
sourceRef="CheckerExclusivegateway" targetRef="endevent">
<conditionExpression xsi:type="tFormalExpression"><![CDATA[${checkerApproved}]]></conditionExpression>
</sequenceFlow>
<sequenceFlow id="Flow_MakerGatewayToEnd"
sourceRef="MakerExclusivegateway" targetRef="endevent">
<conditionExpression xsi:type="tFormalExpression"><![CDATA[${!makerApproved}]]></conditionExpression>
</sequenceFlow>
<sequenceFlow id="Flow_CheckerGatewayToMaker"
sourceRef="CheckerExclusivegateway" targetRef="maker">
<conditionExpression xsi:type="tFormalExpression"><![CDATA[${!checkerApproved}]]></conditionExpression>
</sequenceFlow>
</process>
</definitions>
Давайте реализуем BPMN flowable процесс для диаграммы процесса, показанной выше.
Теперь давайте также реализуем код, который использует вышеприведённое определение процесса для построения системы жалоб.
Контроллер:
Этот класс содержит 5 методов, каждый из которых имеет определённую аннотацию отображения HTTP-запроса:
@PostMapping("maker/request/{complain}")
— используется для обработки HTTP POST-запросов с URI "/api/maker/request/{complain}". Этот метод будет приниматьcomplain
как переменную пути, передавать её методуflowableService.makerRequest
и возвращать ответ от него.@GetMapping("checker/pending/tasks")
— используется для обработки HTTP GET-запросов с URI "/api/checker/pending/tasks". Этот метод получит отложенные задачи checker-а и вернёт их в качестве ответа.@PostMapping("/checker/review/task/{processId}/{approve}")
— используется для обработки HTTP POST-запросов с URI "/api/checker/review/task/{processId}/{approve}". Этот метод приметprocessId
иapprove
как переменные пути, передаст их методуflowableService.checkerReview
и вернёт ответ от него.@GetMapping("maker/return/pending/tasks")
— используется для обработки HTTP GET-запросов с URI "/api/maker/return/pending/tasks". Этот метод получит отложенные задачи инициатора и вернёт их в качестве ответа.@PostMapping("/makerReview/{complain}/{processId}/{approve}")
— используется для обработки HTTP POST-запросов с URI "/api/makerReview/{complain}/{processId}/{approve}". Этот метод приметcomplain
,processId
иapprove
в качестве переменных пути и передаст их во flowable-сервис.
Все эти методы будут взаимодействовать с flowableService
для выполнения определённого действия и возврата ответа.
package com.mediumBlog.Flowabledemo.controller;
import java.util.HashMap;
import java.util.Map;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import org.flowable.engine.ProcessEngine;
import org.flowable.engine.RepositoryService;
import org.flowable.engine.RuntimeService;
import org.flowable.engine.TaskService;
import org.flowable.engine.repository.Deployment;
import org.flowable.engine.runtime.ProcessInstance;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import com.mediumBlog.Flowabledemo.service.FlowableService;
@RestController
@RequestMapping("/api/")
public class Controller {
@Autowired
private FlowableService flowableService;
/*
* This api is to initiate a complain request and assign to checker
*/
@PostMapping("maker/request/{complain}")
public String makerRequest(@PathVariable("complain") String complaint) {
String respons = flowableService.makerRequest(complaint);
return respons;
}
/*
* This api is to get checker pending tasks which are assigned by maker
*/
@GetMapping("checker/pending/tasks")
public Map<String, Object> checkerPending() {
Map<String, Object> response = new HashMap<String, Object>();
response = flowableService.getCheckerPendings();
return response;
}
/*
* This api is to review the task which are pending at checker
*/
@PostMapping("/checker/review/task/{processId}/{approve}")
public String checkerReviewReview(@PathVariable("processId") String processId,
@PathVariable("approve") Boolean approve) {
String respons = flowableService.checkerReview(processId, approve);
return respons;
}
/*
* This api is to get maker return pending tasks which are assigned and rejected by checker level
*/
@GetMapping("maker/return/pending/tasks")
public Map<String, Object> getMakerReturnPendings() {
Map<String, Object> response = new HashMap<String, Object>();
response = flowableService.getMakerReturnPendings();
return response;
}
/*
* This api is to review the maker return pending tasks which are pending at maker level
*/
@PostMapping("/makerReview/{complain}/{processId}/{approve}")
public String makerReviewReturn(@PathVariable("complain") String complain,
@PathVariable("processId") String processId, @PathVariable("approve") Boolean approve) {
String respons = flowableService.makerReviewReturn(complain, processId, approve);
return respons;
}
}
Сервис:
package com.mediumBlog.Flowabledemo.service;
import java.util.Map;
public interface FlowableService {
public String makerRequest(String complain);
public Map<String,Object> getCheckerPendings();
public String checkerReview(String processId,Boolean review);
public Map<String,Object> getMakerReturnPendings();
public String makerReviewReturn(String complaincomplain,String processId, Boolean review);
}
Реализация сервиса
Этот класс реализует интерфейс FlowableService
и реализует методы, объявленные в этом интерфейсе.
Deployment
представляет собой коллекцию определений процессов, форм и других ресурсов, которые развёртываются на движке.RepositorySericve
используется для управления определениями процессов, развёртываниями и другими артефактами в движке Flowable. Создание и управление развёртыванием осуществляется через API RepositoryService, который предоставляет методы для работы с определениями процессов, формами и другими ресурсами.Метод
createDeployment()
вызывается на объектеrepositoryService
для создания нового развёртывания. МетодaddClasspathResource()
добавляет определение процесса BPMN 2.0 в развертывание из файла "flowableProcess.bpmn20.xml" вclasspath
.Метод
startProcessInstanceByKey()
запускает новый экземпляр процесса по ключу, передаваемому в качестве аргумента.Task
представляет одну задачу для пользователя.Затем создается запрос задачи с помощью метода
createTaskQuery()
на объектеTaskService
. МетодprocessDefinitionKey()
указывает ключ определения процесса для задачи, а методprocessInstanceId(processInstance.getId())
связывает задачу с идентификатором экземпляра процесса.Метод
singleResult()
возвращает одну задачу, соответствующую критериям запроса.Метод
setVariables(task.getId(), variables)
устанавливает несколько переменных для задачи, гдеtask.getId()
— это идентификатор задачи, аvariables
— карта переменных.Метод
setVariable(task.getId(), "makerApproved", true)
устанавливает переменную "makerApproved" в true для задачи.Метод
complete(task.getId())
завершает задачу, позволяя экземпляру процесса перейти к следующему шагу.Метод
getVariables(task.getId())
используется для получения переменных, которые были заданы для задачи, гдеtask.getId()
— это id задачи. Этот метод вернёт карту с переменными, которые были заданы для задачи, где ключ — это имя переменной, а значение — это значение переменной.
package com.mediumBlog.Flowabledemo.serviceImpl;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import org.flowable.task.api.Task;
import org.flowable.engine.ProcessEngine;
import org.flowable.engine.RepositoryService;
import org.flowable.engine.RuntimeService;
import org.flowable.engine.TaskService;
import org.flowable.engine.repository.Deployment;
import org.flowable.engine.runtime.ProcessInstance;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.mediumBlog.Flowabledemo.dao.TaskDetails;
import com.mediumBlog.Flowabledemo.entity.Complaint;
import com.mediumBlog.Flowabledemo.repo.FlowableRepo;
import com.mediumBlog.Flowabledemo.service.FlowableService;
@Service
public class FlowableServiceImpl implements FlowableService {
@Autowired
private RepositoryService repositoryService;
@Autowired
private RuntimeService runtimeService;
@PersistenceContext
private EntityManager entityManager;
@Autowired
private TaskService taskService;
@Autowired
private FlowableRepo flowableRepo;
ProcessEngine processEngine;
@Override
public String makerRequest(String complain) {
Map<String, Object> variables = new HashMap<String, Object>();
String response = "";
try {
Deployment deployment = repositoryService.createDeployment()
.addClasspathResource("flowableProcess.bpmn20.xml").deploy();
ProcessInstance processInstance = runtimeService.startProcessInstanceByKey("FlowableProcess", variables);
Task task = taskService.createTaskQuery().processDefinitionKey("FlowableProcess")
.processInstanceId(processInstance.getId()).singleResult();
variables.put("RequestBy", "maker");
variables.put("processId", processInstance.getProcessInstanceId() + "");
variables.put("complain", complain);
taskService.setVariables(task.getId(), variables);
taskService.setVariable(task.getId(), "makerApproved", true);
taskService.complete(task.getId());
response = "Succesfully Done with processId :" + processInstance.getProcessInstanceId() + "";
} catch (Exception e) {
// TODO: handle exception
response = "Error Occured While Maker Requesting :" + e.getMessage();
}
return response;
}
@Override
public Map<String, Object> getCheckerPendings() {
Map<String, Object> var = new HashMap<String, Object>();
try {
List<Task> tasks = new ArrayList<Task>();
tasks = taskService.createTaskQuery().processDefinitionKey("FlowableProcess").taskOwner("checker")
.orderByTaskCreateTime().desc().list();
List<TaskDetails> taskList = getTaskDetails(tasks);
var.put("data", taskList);
var.put("00", "Process Ok");
} catch (Exception e) {
var.put("01", "process failed" + e.getMessage());
}
return var;
}
@Override
public String checkerReview(String processId, Boolean approve) {
String response = "";
try {
Task task = taskService.createTaskQuery().processDefinitionKey("FlowableProcess").taskOwner("checker").processInstanceId(processId)
.singleResult();
if (task == null) {
return response = "There is no task available with processId :" + processId;
}
// getting variable data which came from maker
Map<String, Object> variables = taskService.getVariables(task.getId());
String complain = (String) variables.get("complain");
variables.put("reviewBy", "checker");
taskService.setVariables(task.getId(), variables);
taskService.setVariable(task.getId(), "checkerApproved", approve);
taskService.complete(task.getId());
response = "Checker Successfully reviewed";
if(approve==true){
Complaint complaint = new Complaint();
complaint.setComplaint(variables.get("complain")+"");
complaint.setComplaintInitiator(variables.get("RequestBy")+"");
complaint.setComplaintApprover(variables.get("reviewBy")+"");
complaint.setDate(new Date());
flowableRepo.save(complaint);
}
} catch (Exception e) {
response = "Error Occured While Maker Requesting :" + e.getMessage();
}
return response;
}
@Override
public Map<String, Object> getMakerReturnPendings() {
Map<String, Object> var = new HashMap<String, Object>();
try {
List<Task> tasks = new ArrayList<Task>();
tasks = taskService.createTaskQuery().processDefinitionKey("FlowableProcess").taskOwner("maker")
.orderByTaskCreateTime().desc().list();
List<TaskDetails> taskList = getTaskDetails(tasks);
var.put("data", taskList);
var.put("00", "Process Ok");
} catch (Exception e) {
var.put("01", "process failed" + e.getMessage());
}
return var;
}
@Override
public String makerReviewReturn(String complain,String processId, Boolean approve) {
String response = "";
try {
Task task = taskService.createTaskQuery().processDefinitionKey("FlowableProcess").taskOwner("maker").processInstanceId(processId)
.singleResult();
if (task == null) {
return response = "There is no task available with processId :" + processId;
}
// getting variable data which came from maker
Map<String, Object> variables = taskService.getVariables(task.getId());
variables.put("complain", complain.equals("") ? variables.get("complain") : complain);
taskService.setVariables(task.getId(), variables);
taskService.setVariable(task.getId(), "makerApproved", approve);
taskService.complete(task.getId());
response = "Maker Successfully reviewed";
} catch (Exception e) {
response = "Error Occured While Maker Requesting :" + e.getMessage();
}
return response;
}
public List<TaskDetails> getTaskDetails(List<Task> tasks) {
List<TaskDetails> taskDetail = new ArrayList<>();
for (Task task : tasks) {
Map<String, Object> variables = new HashMap<String, Object>();
Map<String, Object> processVariable = taskService.getVariables(task.getId());
taskDetail.add(new TaskDetails(task.getId(), task.getName(), task.getCreateTime(), processVariable));
}
return taskDetail;
}
}
Entity класс
Этот класс будет использоваться интерфейсом FlowableRepo
для выполнения CRUD-операций с базой данных.
package com.mediumBlog.Flowabledemo.entity;
import java.util.Date;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import lombok.Data;
@Entity
@Data
public class Complaint {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String complaint;
private String complaintInitiator;
private String complaintApprover;
private Date date;
}
Детали задачи:
Класс "TaskDetails" в пакете используется для хранения информации о текущей задаче и её данных.
Он имеет четыре поля:
"taskId" типа "String": id задачи.
"taskName" типа "String": имя задачи.
"updatedDate" типа "Date", дата обновления задачи.
"taskData" типа "Map<String, Object>": данные задачи с парой ключ-значение.
Этот класс используется в классе FlowableServiceImpl
для хранения данных о задаче, и, скорее всего, он будет возвращен контроллеру как часть ответа, который будет передан на фронтенд.
package com.mediumBlog.Flowabledemo.dao;
import java.util.Date;
import java.util.Map;
import lombok.Data;
@Data
public class TaskDetails {
String taskId;
String taskName;
Date updatedDate;
public Map<String, Object> taskData;
public TaskDetails(String taskId, String taskName, Date updatedDate, Map<String, Object> taskData) {
super();
this.taskId = taskId;
this.taskName = taskName;
this.updatedDate = updatedDate;
this.taskData = taskData;
}
}
Таблицы данных времени выполнения в Activiti
Данные времени выполнения в Activiti хранятся только во время выполнения экземпляра процесса, и когда процесс завершается, записи удаляются.
Таблицы ACT_RU_TASK и ACT_RU_VARIABLE являются частью данных времени выполнения в Activiti. В таблице ACT_RU_TASK хранится информация о текущих задачах, такие как исполнитель, сроки и статус выполнения. ACT_RU_VARIABLE содержит переменные, связанные с задачами или процессами, и обе таблицы связаны внешним ключом PROC_INST_ID_ и TASK_ID_.
Для проверки завершенных процессов мы можем получить подробности из ACT_HI_TASKINST и ACT_HI_VARINST. ACT_HI_TASKINST содержит данные о завершенных заданиях, включая исполнителя, время начала и окончания. Таблица ACT_HI_VARINST содержит данные о переменных процесса, их значения и типы. Эти таблицы используются для аудита и отчётности, например, для отслеживания хода процесса или анализа производительности.
Заключение
В этой статье мы рассмотрели реализацию модели бизнес-процессов и узнали, как создать мощное и гибкое решение по управлению рабочими процессами и BPM-приложениями, объединив Flowable и Spring Boot.
Весь код, упомянутый в этой статье, доступен на GitHub.
Как правильно использовать шлюзы при моделировании процессов в нотации BPMN? Обсудим это на открытом уроке 15 апреля. Записаться можно на странице онлайн-курса.