BPMN (англ. Business Process Modeling Notation, «нотация и модель бизнес-процессов») — это язык визуального моделирования бизнес-процессов, использующий графические блок-схемы. Это открытый стандарт, созданный консорциумом Object Management Group (OMG).

Основными целями BPMN являются:

  • Достижение большей гибкости бизнеса.

  • Достижение более высоких уровней эффективности и результативности.

  • Повышение производительности с точки зрения качества, затрат и сроков.

Object Management Group определила ряд стандартов и нотаций, известных как BPM (англ. “Business Process Management”, «управление бизнес-процессами»).

Процессный движок Flowable позволяет разворачивать процессы в соответствии с международным отраслевым стандартом BPMN 2.0. Каждый процесс BPM представляет собой последовательность объектов, связанных с действиями и имеющих стартовое и конечное события.

BPMN используется для автоматизации бизнеса — например, в управлении пользовательским/клиентским опытом или управлении мероприятиями. Он упрощает и ускоряет разработку, уменьшая количество ошибок.

Блок-схема Flowable
Блок-схема Flowable

Стартовое событие (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 апреля. Записаться можно на странице онлайн-курса.

Комментарии (0)