Tuesday, February 11, 2014

Starting with Acitiviti (BPM Engine)

Background

I am busy working on a project which has a very small workflow or business process component to it. One of the requirements was that we had to use Activiti to manage the business processes. My first port of call was the "Activiti in Action" book. When I felt that had enough ammunition to take on Activiti, I gave it a go.

There were a few bumps along the road especially because I was trying to incorporate Acitiviti into an existing Spring Maven application. In this blog I am going to show you how to get your Activiti code running in a Maven Spring application.

Activiti Architecture

The Basic Activiti architecture is pretty simple. There is a database which is used to maintain and persist state. You can manipulate the state of objects within the database by using the Activiti engine or API. This can be the Activiti libraries or dependencies in your application or you can also interrogate the database using the Rest services.

The Activiti Explorer is a web application which uses the API to view and change objects housed in the database. The Activiti Explorer also has a tool (Activiti Modeler) which can be used to model BPMN processes. You can model your process using this tool and then export the configuration into an xml file. You can then use this xml file in your application without having to model the process manually. 

Downloads

You will need to download Tomcat and the latest Activiti Release. The only 2 artefacts of concern to you for this tutorial can be located in the wars directory:
  • activiti-rest.war
  • activiti-explorer.war 
You can deploy these 2 war files on your Tomcat server. To be really specific, you only need the activiti-explorer. You might need to add the H2 database Driver to your Tomcat server.

Do I need a database?

Activiti does use a database but you do not need one explicitly. Activiti is packaged with an embedded H2 database. This means that when you run the application, it will access this embedded database automatically. So even though you are not specifying a database, there is one being used.

Add these dependencies to your Maven POM file

     <!-- Activiti Stuff-->  
     <dependency>  
       <groupId>org.activiti</groupId>  
       <artifactId>activiti-engine</artifactId>  
       <version>5.14</version>  
     </dependency>  
     <dependency>  
       <groupId>org.activiti</groupId>  
       <artifactId>activiti-spring</artifactId>  
       <version>5.14</version>  
     </dependency>  
 <!-- Spring Stuff-->  
     <dependency>  
       <groupId>org.springframework</groupId>  
       <artifactId>spring-context</artifactId>  
       <version>${spring.version}</version>  
     </dependency>  
     <dependency>  
       <groupId>org.springframework</groupId>  
       <artifactId>spring-jdbc</artifactId>  
       <version>${spring.version}</version>  
     </dependency>  
     <dependency>  
       <groupId>org.springframework</groupId>  
       <artifactId>spring-tx</artifactId>  
       <version>${spring.version}</version>  
     </dependency>  
 <!-- H2 Driver-->  
     <dependency>  
       <groupId>com.h2database</groupId>  
       <artifactId>h2</artifactId>  
       <version>1.3.175</version>  
     </dependency>  

Add this repository to your Artefact repository or your Maven POM file

     <repository>  
       <id>Alfresco Maven Repository</id>  
       <url>https://maven.alfresco.com/nexus/content/groups/public/</url>  
     </repository>  

Design a Simple Activiti Workflow process using Activiti Modeler

This is the sample process that I designed. It is simple and self explanatory.
Here is the exported xml file contents (SampleActivitiProcess.bpmn20.xml):
 <?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/processdef">  
  <process id="SampleActivitiProcess" name="SampleActiviti" isExecutable="true">  
   <startEvent id="start" name="start"></startEvent>  
   <userTask id="SomeUserTask" name="SomeUserTask"></userTask>  
   <endEvent id="end" name="end"></endEvent>  
   <sequenceFlow id="flowFromStart" name="flowFromStart" sourceRef="start" targetRef="SomeUserTask"></sequenceFlow>  
   <sequenceFlow id="flowToEnd" name="flowToEnd" sourceRef="SomeUserTask" targetRef="end"></sequenceFlow>  
  </process>  
  <bpmndi:BPMNDiagram id="BPMNDiagram_SampleActivitiProcess">  
   <bpmndi:BPMNPlane bpmnElement="SampleActivitiProcess" id="BPMNPlane_SampleActivitiProcess">  
    <bpmndi:BPMNShape bpmnElement="start" id="BPMNShape_start">  
     <omgdc:Bounds height="30.0" width="30.0" x="165.0" y="210.0"></omgdc:Bounds>  
    </bpmndi:BPMNShape>  
    <bpmndi:BPMNShape bpmnElement="SomeUserTask" id="BPMNShape_SomeUserTask">  
     <omgdc:Bounds height="80.0" width="100.0" x="301.0" y="185.0"></omgdc:Bounds>  
    </bpmndi:BPMNShape>  
    <bpmndi:BPMNShape bpmnElement="end" id="BPMNShape_end">  
     <omgdc:Bounds height="28.0" width="28.0" x="495.0" y="211.0"></omgdc:Bounds>  
    </bpmndi:BPMNShape>  
    <bpmndi:BPMNEdge bpmnElement="flowFromStart" id="BPMNEdge_flowFromStart">  
     <omgdi:waypoint x="195.0" y="225.0"></omgdi:waypoint>  
     <omgdi:waypoint x="301.0" y="225.0"></omgdi:waypoint>  
    </bpmndi:BPMNEdge>  
    <bpmndi:BPMNEdge bpmnElement="flowToEnd" id="BPMNEdge_flowToEnd">  
     <omgdi:waypoint x="401.0" y="225.0"></omgdi:waypoint>  
     <omgdi:waypoint x="495.0" y="225.0"></omgdi:waypoint>  
    </bpmndi:BPMNEdge>  
   </bpmndi:BPMNPlane>  
  </bpmndi:BPMNDiagram>  
 </definitions>  
I would advise you to always change the ID's of all of the elements and also provide them with descriptive names. This helps you out tremendously when developing against your BPMN process.

Let us start Coding

The general process of running an Activiti process is as follows:

Design BPMN Process

You can use Activiti Modeler from Activiti Explorer, the Eclipse Activiti Plugin or manually build the xml representing your business process flow.

Deploy Process

You can use Activiti Explorer to deploy or programatically deploy your business process flow. Doing it programatically, you have 2 options:

  • The Spring way
  • The Standard java way

Run Process Instance

You reference a process which is deployed and start a new process instance.

Create Activiti process engine

The are 3 ways to achieve this:

In Memory

 ProcessEngine processEngine = ProcessEngineConfiguration  
         .createStandaloneInMemProcessEngineConfiguration()  
         .buildProcessEngine();  

This is the easiest and fastest way to get up and running. It references the embedded database and uses the default configuration.

File based

 ProcessEngine processEngine = ProcessEngineConfiguration.createProcessEngineConfigurationFromResource("activiti.cfg.xml").buildProcessEngine();  
The database connection settings are referenced from the activiti.cfg.xml

Here is a sample file refencing the embedded default H2 database:
 <?xml version="1.0" encoding="UTF-8"?>  
 <beans xmlns="http://www.springframework.org/schema/beans"  
     xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"  
     xsi:schemaLocation="http://www.springframework.org/schema/beans  
  http://www.springframework.org/schema/beans/spring-beans.xsd">  
   <!--<bean id="processEngineConfiguration"  
      class="org.activiti.engine.impl.cfg.StandaloneInMemProcessEngineConfiguration">-->  
   <bean id="processEngineConfiguration" class="org.activiti.engine.impl.cfg.StandaloneProcessEngineConfiguration">  
     <property name="jdbcUrl"  
          value="jdbc:h2:/tmp/activiti;AUTO_SERVER=TRUE" />  
     <property name="jdbcDriver" value="org.h2.Driver" />  
     <property name="jdbcUsername" value="sa" />  
     <property name="jdbcPassword" value="" />  
     <!-- Database configurations -->  
     <property name="databaseSchemaUpdate" value="true" />  
     <!-- job executor configurations -->  
     <property name="jobExecutorActivate" value="false" />  
     <!-- mail server configurations -->  
     <property name="mailServerPort" value="5025" />  
     <property name="history" value="full" />  
   </bean>  
 </beans>  

Basic Spring Style

     ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("Service-core-application-context.xml");  
     ProcessEngine processEngine = (ProcessEngine) ctx.getBean("processEngine");  

Here is the Spring configuration required:
   <bean id="dataSource" class="org.springframework.jdbc.datasource.SimpleDriverDataSource">  
     <property name="driverClass" 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>  
   <bean id="processEngineConfiguration" class="org.activiti.spring.SpringProcessEngineConfiguration">  
     <property name="databaseType" value="mysql" />  
     <property name="dataSource" ref="dataSource" />  
     <property name="transactionManager" ref="transactionManager" />  
     <property name="databaseSchemaUpdate" value="true" />  
     <property name="deploymentResources"  
          value="classpath*:SampleActivitiProcess.bpmn20.xml" />  
     <property name="jobExecutorActivate" value="false" />  
   </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" />  
You can see in this way, Spring handles deploying the BPMN Process for you.

It is important to know that BPMN Process xml files can only be of 2 types and have to end in the following extensions for the API to process / deploy the files:

  1. .bpmn20.xml (Activiti Modeler)
  2. .bpmn (Eclipse Activit Plugin)
The 2 services that we require is the RuntimeService and the RepositoryService:
 RepositoryService repositoryService = processEngine.getRepositoryService();  
 RuntimeService runtimeService = processEngine.getRuntimeService();  

Remember that if you are using the Spring way, the respective beans already exist.
We use the RepositoryService to deploy the process, if it has not already been done (the spring way or via the Activiti Explorer console):
 repositoryService.createDeployment()  
         .addClasspathResource("SampleActivitiProcess.bpmn20.xml")  
         .deploy();  

We then start a process using the RuntimeService:
 String procId = runtimeService.startProcessInstanceByKey("SampleActivitiProcess").getId();  

A process is considered to be active if it is not in an end state or not suspended. You can use the Task Service to manipulate active tasks:
 TaskService taskService = processEngine.getTaskService();  
 List<Task> tasks = taskService.createTaskQuery().active().list();  
Remember that the TaskService bean already exists if you are using the Spring way.

You can use the TaskService to query, claim and complete tasks:
 taskService.claim(task.getId(), "sampleUser");  
 taskService.complete(task.getId());  

Once the Process in a Final state, you can't use the TaskService for these processes but need to use the HistoryService:
 HistoryService historyService = processEngine.getHistoryService();  
     HistoricProcessInstance historicProcessInstance =  
         historyService.createHistoricProcessInstanceQuery().processInstanceId(procId).singleResult();  
     System.out.println("Process instance end time: " + historicProcessInstance.getEndTime());  
Remember that the HistoryService bean already exists if you are using the Spring way.

Conclusion

This should be enough information to get you up and running with Activiti. Remember to start off small and build on incrementally. This way you can understand the process in much greater detail.
Activiti is a great tool and what is even more amazing is that this piece of software is open source and free to use.

As always, I would love to hear your comments or questions. You can also follow me on Twitter or Google plus. I have added shortcuts to my profile in the About me link.


No comments:

Post a Comment