[Перевод] Руководство по интеграции Flowable с Spring Boot

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 (тот, кто подтверждает запрос). Создатель инициирует запрос, а проверяющий должен подтвердить его для завершения процесса.

f8dea6b1200f3f55036fa7c16fbb14b0.png

Начнём с кода:

POM:

Файл pom.xml, представленный ниже, содержит зависимости, используемые для реализации BPMN.


   org.flowable
   flowable5-spring-compatibility
   ${flowable.version}

В процессном движке Flowable:

  • Элемент является корневым элементом файла определения процесса. Он используется для определения набора процессов, диаграмм взаимодействия и других артефактов, которые являются частью бизнес-процесса.

  • Элемент определяет сам бизнес-процесс и содержит начальные и конечные события, а также другие элементы, такие как , , и .

  • Элемент обозначает начало процесса или подпроцесса.

  • представляет работу, выполняемую пользователем.

  • обозначает поток управления между действиями в процессе или подпроцессе.

  • exclusiveGateway — тип шлюза, который используется для обеспечения условного разветвления в процессе или подпроцессе. Он имеет один вход и несколько выходов, и принимает решение о том, какой исходящий поток выбрать на основе заданного условия. Каждый исходящий поток обычно ассоциируется с выражением условия, и шлюз выбирает первый поток, для которого условие оценивается как истинное (true). Если ни одно из условий не является истинным, выбирается поток по умолчанию.

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

  • conditionExpression — это булево выражение, которое используется для определения того, следует ли выполнять последовательность действий или нет. Обычно оно используется в сочетании с исключающим (exclusive) шлюзом, который позволяет процессу разветвляться в разных направлениях на основе оценки условия.



 
  
  
  
  
  
  
  
   
  
  
  
  
  
   
  
  
   
  
  
   
  
 

ef914ab5252e78730301dc49ef162b3d.png

Давайте реализуем 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 checkerPending() {
  Map response = new HashMap();
  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 getMakerReturnPendings() {
  Map response = new HashMap();
  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 getCheckerPendings();
 public String checkerReview(String processId,Boolean review);
 public Map 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 variables = new HashMap();
  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 getCheckerPendings() {

  Map var = new HashMap();

  try {

   List tasks = new ArrayList();

   tasks = taskService.createTaskQuery().processDefinitionKey("FlowableProcess").taskOwner("checker")
     .orderByTaskCreateTime().desc().list();

   List 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 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 getMakerReturnPendings() {
  
  Map var = new HashMap();

  try {

   List tasks = new ArrayList();

   tasks = taskService.createTaskQuery().processDefinitionKey("FlowableProcess").taskOwner("maker")
     .orderByTaskCreateTime().desc().list();

   List 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 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 getTaskDetails(List tasks) { 
  List taskDetail = new ArrayList<>(); 
  for (Task task : tasks) {
   Map variables = new HashMap();
   Map 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»: данные задачи с парой ключ-значение.

Этот класс используется в классе 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 taskData;

     public TaskDetails(String taskId, String taskName, Date updatedDate, Map 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 апреля. Записаться можно на странице онлайн-курса.

© Habrahabr.ru