Как стать автором
Обновить

«Hello World» BPMN2.0 или введение в Activiti

Время на прочтение12 мин
Количество просмотров34K
В данной статье я хочу рассмотреть пример создания простого приложения с использованием движка Activiti.

Activiti” — это легковесная платформа (framework) для работы с бизнес-процессами (Business Process Managment), адаптированная для деловых людей, разработчиков и системных администраторов. Платформа основана на быстром и надёжном java-движке BPMN2-процессов. Проект OpenSource’ный и распространяется под лицензией Apache. Activiti может запускаться либо как часть вашего java-приложения, либо самостоятельно на сервере, кластере или облаке. Кроме того, она прекрасно интегрируется со Spring’ом.

По скольку проект обладает прекрасной документацией, я не буду описывать его основные концепции. Замечу только, что, как уже говорилось выше, мы можем использовать Activiti как самостоятельное приложение, а можем как движок/библиотеку для обработки бизнес-процессов, встраиваемый в приложение. Подробности можно посмотреть тут.

Зависимости


И так, начнём. Первым делом нам нужно создать проект с поддержкой Maven и описать в нём следующие зависимости:

    <!--
      Activiti
    -->
    <dependency>
      <groupId>org.activiti</groupId>
      <artifactId>activiti-engine</artifactId>
      <version>5.1</version>
    </dependency>
    <dependency>
      <groupId>org.activiti</groupId>
      <artifactId>activiti-spring</artifactId>
      <version>5.1</version>
    </dependency>
   
   
    <!--
      DataBase
    -->
    <dependency>
      <groupId>com.h2database</groupId>
      <artifactId>h2</artifactId>
      <version>1.2.132</version>
    </dependency>
    <dependency>
      <groupId>commons-dbcp</groupId>
      <artifactId>commons-dbcp</artifactId>
      <version>1.2.2</version>
    </dependency>


* This source code was highlighted with Source Code Highlighter.


Первые две зависимости (репозитарии для maven’a описаны тут) подключают ядро Activiti и набор утилит для интеграции со Spring’ом (подключение Spring’a не описано для экономии места). Вторая группа зависимостей подключает БД (в нашем случае будет использована in-memory) и пул для работы с ней.

База данных нужна Activiti для хранения информации о процессах, их состоянии, пользователях, истории и многом-многом другом.

Бизнес-процесс


Логично начать разработку приложения с описания бизнес-процесса. Прежде всего стоит ознакомиться с единицами описания бизнес-процессов, которые нам предлагает Activiti (точнее нотация BPMN 2.0). После этого можно приступать к описанию своего собственного бизнес-процесса. Сделать это можно тремя способами:
  • с помощью плагина к Eclipse (скудная палитра компонентов);
  • с помощью специального приложения Activiti Modeler (нет возможности загрузки диаграмм, крайне странно ведёт себя сохранение);
  • руками через XML-редактор.

Не смотря на то, что самым логичным из перечисленного кажется третий пункт, мы всё же воспользуемся CASE-технологиями и нарисуем наш бизнес процесс с помощью плагина для Eclipse (важно выставить настройках плагина обновление xml-описания после каждого изменения диагрммы, иначе редактор начинает жить своей собственной жизнью и полностью игнорировать внешние раздражители в виде пользователя).

В результате у меня получилась следующая диаграмма:

diagram

ServiceTask “GenerateData” генерирует псевдослучайные данные (реализация с помощью класса GenerateDataService, имплементирующего интерфейс JavaDelegate)

@Service
public class GenerateDataService implements JavaDelegate {
 
  public void execute(DelegateExecution execution) throws Exception {
    Long someData = Calendar.getInstance().getTimeInMillis() % 2;    
    execution.setVariable("someData", someData);
  }
}


* This source code was highlighted with Source Code Highlighter.


в зависимости от которых выполняется либо ServiceTask “SayHelloA”, либо ServiceTask “SayHelloB”. Оба эти ServiceTask’a сводятся к вызову соответствующего метода службы SayHelloService (реализация на основе UEL-выражения "${sayHelloService.printMessageB(execution)"):

@Service
public class SayHelloService {
  
  public void printMessageA(ActivityExecution execution) {
    System.out.println("Hello world: variant A");
  }
  
  public void printMessageB(ActivityExecution execution) {
    System.out.println("Hello world: variant B");
  }
}


* This source code was highlighted with Source Code Highlighter.


В XML-виде (то есть в формате BPMN 2.0), а именно он нужен движку Activiti для интерпретации, процесс выглядит следующим образом:

<?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: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="helloWorldProcess" name="Hello World">
  <documentation>Simple "Hello World" process</documentation>
  <startEvent id="startevent1" name="Start"></startEvent>
  <serviceTask id="servicetask1" name="GenerateData" activiti:class="name.krestjaninoff.activiti.hello.process.GenerateDataService"></serviceTask>
  <endEvent id="endevent1" name="End"></endEvent>
  <sequenceFlow id="flow1" name="" sourceRef="startevent1" targetRef="servicetask1"></sequenceFlow>
  <serviceTask id="servicetask3" name="SayHelloB" activiti:expression="${sayHelloService.printMessageB(execution)}"></serviceTask>
  <serviceTask id="servicetask4" name="SayHelloA" activiti:expression="${sayHelloService.printMessageA(execution)}"></serviceTask>
  <exclusiveGateway id="exclusivegateway2" name="Exclusive Gateway"></exclusiveGateway>
  <sequenceFlow id="flow4" name="" sourceRef="servicetask1" targetRef="exclusivegateway2"></sequenceFlow>
  <sequenceFlow id="flow5" name="${someData != 0}" sourceRef="exclusivegateway2" targetRef="servicetask3">
   <conditionExpression xsi:type="tFormalExpression"><![CDATA[${someData != 0}]]></conditionExpression>
  </sequenceFlow>
  <sequenceFlow id="flow6" name="${someData == 0}" sourceRef="exclusivegateway2" targetRef="servicetask4">
   <conditionExpression xsi:type="tFormalExpression"><![CDATA[${someData == 0}]]></conditionExpression>
  </sequenceFlow>
  <exclusiveGateway id="exclusivegateway3" name="Exclusive Gateway"></exclusiveGateway>
  <sequenceFlow id="flow7" name="" sourceRef="servicetask3" targetRef="exclusivegateway3"></sequenceFlow>
  <sequenceFlow id="flow8" name="" sourceRef="servicetask4" targetRef="exclusivegateway3"></sequenceFlow>
  <sequenceFlow id="flow9" name="" sourceRef="exclusivegateway3" targetRef="endevent1"></sequenceFlow>
</process>

<bpmndi:BPMNDiagram id="BPMNDiagram_helloWorldProcess">
  <bpmndi:BPMNPlane bpmnElement="helloWorldProcess" id="BPMNPlane_helloWorldProcess">
   <bpmndi:BPMNShape bpmnElement="startevent1" id="BPMNShape_startevent1">
    <omgdc:Bounds height="55" width="55" x="273" y="10"></omgdc:Bounds>
   </bpmndi:BPMNShape>
   <bpmndi:BPMNShape bpmnElement="servicetask1" id="BPMNShape_servicetask1">
    <omgdc:Bounds height="55" width="105" x="248" y="120"></omgdc:Bounds>
   </bpmndi:BPMNShape>
   <bpmndi:BPMNShape bpmnElement="endevent1" id="BPMNShape_endevent1">
    <omgdc:Bounds height="55" width="55" x="273" y="460"></omgdc:Bounds>
   </bpmndi:BPMNShape>
   <bpmndi:BPMNShape bpmnElement="servicetask3" id="BPMNShape_servicetask3">
    <omgdc:Bounds height="55" width="105" x="390" y="304"></omgdc:Bounds>
   </bpmndi:BPMNShape>
   <bpmndi:BPMNShape bpmnElement="servicetask4" id="BPMNShape_servicetask4">
    <omgdc:Bounds height="55" width="105" x="109" y="304"></omgdc:Bounds>
   </bpmndi:BPMNShape>
   <bpmndi:BPMNShape bpmnElement="exclusivegateway2" id="BPMNShape_exclusivegateway2">
    <omgdc:Bounds height="60" width="60" x="270" y="210"></omgdc:Bounds>
   </bpmndi:BPMNShape>
   <bpmndi:BPMNShape bpmnElement="exclusivegateway3" id="BPMNShape_exclusivegateway3">
    <omgdc:Bounds height="60" width="60" x="270" y="370"></omgdc:Bounds>
   </bpmndi:BPMNShape>
   <bpmndi:BPMNEdge bpmnElement="flow1" id="BPMNEdge_flow1">
    <omgdi:waypoint x="328" y="37"></omgdi:waypoint>
    <omgdi:waypoint x="248" y="147"></omgdi:waypoint>
   </bpmndi:BPMNEdge>
   <bpmndi:BPMNEdge bpmnElement="flow4" id="BPMNEdge_flow4">
    <omgdi:waypoint x="353" y="147"></omgdi:waypoint>
    <omgdi:waypoint x="270" y="240"></omgdi:waypoint>
   </bpmndi:BPMNEdge>
   <bpmndi:BPMNEdge bpmnElement="flow5" id="BPMNEdge_flow5">
    <omgdi:waypoint x="330" y="240"></omgdi:waypoint>
    <omgdi:waypoint x="390" y="331"></omgdi:waypoint>
   </bpmndi:BPMNEdge>
   <bpmndi:BPMNEdge bpmnElement="flow6" id="BPMNEdge_flow6">
    <omgdi:waypoint x="330" y="240"></omgdi:waypoint>
    <omgdi:waypoint x="109" y="331"></omgdi:waypoint>
   </bpmndi:BPMNEdge>
   <bpmndi:BPMNEdge bpmnElement="flow7" id="BPMNEdge_flow7">
    <omgdi:waypoint x="495" y="331"></omgdi:waypoint>
    <omgdi:waypoint x="270" y="400"></omgdi:waypoint>
   </bpmndi:BPMNEdge>
   <bpmndi:BPMNEdge bpmnElement="flow8" id="BPMNEdge_flow8">
    <omgdi:waypoint x="214" y="331"></omgdi:waypoint>
    <omgdi:waypoint x="270" y="400"></omgdi:waypoint>
   </bpmndi:BPMNEdge>
   <bpmndi:BPMNEdge bpmnElement="flow9" id="BPMNEdge_flow9">
    <omgdi:waypoint x="330" y="400"></omgdi:waypoint>
    <omgdi:waypoint x="273" y="487"></omgdi:waypoint>
   </bpmndi:BPMNEdge>
  </bpmndi:BPMNPlane>
</bpmndi:BPMNDiagram>
</definitions>

* This source code was highlighted with Source Code Highlighter.


Настройки


Теперь для, того что бы мы могли запустить процесс с помощью Activiti, необходимо настроить и инициализировать основные элементы ядра Activity. По скольку мы используем Spring, для нас настройка сведётся к конфигурации соответствующих bean’ов:

<beans xmlns="http://www.springframework.org/schema/beans"
  xmlns:context="http://www.springframework.org/schema/context"
  xmlns:tx="http://www.springframework.org/schema/tx"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://www.springframework.org/schema/beans www.springframework.org/schema/beans/spring-beans.xsd
    www.springframework.org/schema/context www.springframework.org/schema/context/spring-context-3.0.xsd
    www.springframework.org/schema/tx www.springframework.org/schema/tx/spring-tx-3.0.xsd"

>
  <!-- Configuration --> 
  <context:property-placeholder location="classpath*:*.properties" />

  <!-- Annotation based configuration -->
  <context:annotation-config />
  <context:component-scan base-package="name.krestjaninoff" />
 
 
  <!-- Data -->
  <bean id="dataSource"
    class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
    <property name="driverClassName" value="org.h2.Driver"/>
    <property name="url" value="jdbc:h2:mem:activiti;DB_CLOSE_DELAY=1000"/>
    <property name="username" value="sa"/>
    <property name="password" value=""/>
  </bean>

  <bean id="transactionManager"
    class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <property name="dataSource" ref="dataSource" />
  </bean>
  <tx:annotation-driven transaction-manager="transactionManager"/>
 
 
  <!--
    Activiti
  -->
  <bean id="processEngineConfiguration" class="org.activiti.spring.SpringProcessEngineConfiguration">
    <property name="databaseType" value="h2" />
    <property name="dataSource" ref="dataSource" />
    <property name="transactionManager" ref="transactionManager" />
    <property name="databaseSchemaUpdate" value="true" />
    <property name="jobExecutorActivate" value="false" />
    <property name="deploymentResources" value="classpath*:/process/*.bpmn20.xml" />
  </bean>
 
  <bean id="processEngine" class="org.activiti.spring.ProcessEngineFactoryBean">
    <property name="processEngineConfiguration" ref="processEngineConfiguration" />
  </bean>

  <bean id="repositoryService" factory-bean="processEngine"
    factory-method="getRepositoryService" />
  <bean id="runtimeService" factory-bean="processEngine"
    factory-method="getRuntimeService" />
  <bean id="taskService" factory-bean="processEngine"
    factory-method="getTaskService" />
  <bean id="historyService" factory-bean="processEngine"
    factory-method="getHistoryService" />
  <bean id="managementService" factory-bean="processEngine"
    factory-method="getManagementService" />
</beans>


* This source code was highlighted with Source Code Highlighter.


Важно отметить свойство deploymentResources bean’а processEngineConfiguration — оно указывает конфигуратору, где расположены файлы описания наших процессов. Каждый раз при старте приложения, необходимо “выложить” (deploy) эти процессы в движок Activiti. Подробнее deployment описан тут.

Запуск приложения


И так, основная работа уже сделана. Теперь для запуска приложения нам останется лишь создать контекст Spring’a и запустить нужный процесс:

public class Main {
  public static void main(String[] args) {
   
    // Create Spring context
    ClassPathXmlApplicationContext applicationContext =
      new ClassPathXmlApplicationContext("applicationContext.xml");
   
    // Start process
    RuntimeService runtimeService = (RuntimeService) applicationContext.
      getBean("runtimeService");
    runtimeService.startProcessInstanceByKey("helloWorldProcess");
  }
}


* This source code was highlighted with Source Code Highlighter.


Пример


Код описанного выше проекта доступен на GitHub.
Теги:
Хабы:
+17
Комментарии21

Публикации

Изменить настройки темы

Истории

Работа

Java разработчик
352 вакансии

Ближайшие события

Weekend Offer в AliExpress
Дата20 – 21 апреля
Время10:00 – 20:00
Место
Онлайн