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

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

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 представляет собой коллекцию определений процессов, форм и других ресурсов, которые развёртываются на движке.

  • RepositoryService используется для управления определениями процессов, развёртываниями и другими артефактами в движке 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 апреля. Записаться можно на странице онлайн-курса.


ссылка на оригинал статьи https://habr.com/ru/articles/806357/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *