Introduction to Activiti with Spring
1. Overview
Simply put, Activiti is a workflow and
Business Process Management platform.
We can get started quickly by creating a ProcessEngineConfiguration (typically
based on a configuration file). From this, we can obtain a ProcessEngine –
and through the ProcessEngine, we can execute workflow &
BPM operations.
The API provides various services that can be used
to access and manage processes. These services can provide us information about
the history of processes, what's currently running and the processes that are
deployed but not running yet.
The services can also be used to define the process
structure and manipulate the state of the process, i.e. run, suspend, cancel,
etc.
If you are new to the API, check out our Introduction
to Activiti API with Java. In this article, we'll discuss how we
can set up Activiti API within a Spring Boot application.
2. Setup with Spring Boot
Let's see how we can setup Activiti as a Spring
Boot Maven application and start using it.
2.1. Initial Setup
As usual, we need to add the maven dependency:
1
2
3
4
|
<dependency>
<groupId>org.activiti</groupId>
<artifactId>activiti-spring-boot-starter-basic</artifactId>
</dependency>
|
The latest stable version of the API can be
found here. It works with Spring Boot up through
v1.5.4. It doesn't work with v2.0.0.M1 yet.
We can also generate a Spring Boot project
using https://start.spring.io and
select Activiti as a dependency.
Just by adding this dependency and the @EnableAutoConfiguration annotation
to the Spring Boot Application, it'll do the initial setup:
- Create Datasource (The API requires a database
to create the ProcessEngine)
- Create and Expose the ProcessEngine bean
- Create and Expose the Activiti services beans
- Create the Spring Job Executor
2.2. Creating and Running a Process
Let's construct an example of creating and running
a business process.
To define a process we will need to create a BPMN
file. For this, we can use https://activiti.alfresco.com/activiti-app/editor to
create a process definition.
Then, just download the BPMN file. We will need to
put this file in the src/main/resources/processes folder. By
default, Spring Boot will look in this folder to deploy the process definition.
We'll create a demo process containing one user
tasks:
The assignee of the user task is set as the
Initiator of the process. The BPMN file for this process definition looks like:
1
2
3
4
5
6
7
8
9
10
11
12
|
<process id="my-process" name="say-hello-process" isExecutable="true">
<startEvent id="startEvent" name="startEvent">
</startEvent>
<sequenceFlow id="sequence-flow-1" sourceRef="startEvent" targetRef="A">
</sequenceFlow>
<userTask id="A" name="A" activiti:assignee="$INITIATOR">
</userTask>
<sequenceFlow id="sequence-flow-2" sourceRef="A" targetRef="endEvent">
</sequenceFlow>
<endEvent id="endEvent" name="endEvent">
</endEvent>
</process>
|
Now, we'll create a REST controller to handle
requests to start this process:
1
2
3
4
5
6
7
8
9
10
11
|
@Autowired
private RuntimeService
runtimeService;
@GetMapping("/start-process")
public String
startProcess() {
runtimeService.startProcessInstanceByKey("my-process");
return "Process
started. Number of currently running"
+
"process instances = "
+
runtimeService.createProcessInstanceQuery().count();
}
|
Here, runtimeService.startProcessInstanceByKey(“my-process”) starts
the execution of the process whose key is “my-process”. runtimeService.createProcessInstanceQuery().count() will
get us the number of process instances.
Every time we hit the path “/start-process”,
a new ProcessInstance will be created and we'll see an
increment in the count of the currently running processes.
A JUnit test case shows us this behavior:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
|
@Test
public void givenProcess_whenStartProcess_thenIncreaseInProcessInstanceCount()
throws Exception {
String
responseBody = this.mockMvc
.perform(MockMvcRequestBuilders.get("/start-process"))
.andReturn().getResponse().getContentAsString();
assertEquals("Process
started. Number of currently running"
+
" process instances = 1", responseBody);
responseBody
= this.mockMvc
.perform(MockMvcRequestBuilders.get("/start-process"))
.andReturn().getResponse().getContentAsString();
assertEquals("Process
started. Number of currently running"
+
" process instances = 2", responseBody);
responseBody
= this.mockMvc
.perform(MockMvcRequestBuilders.get("/start-process"))
.andReturn().getResponse().getContentAsString();
assertEquals("Process
started. Number of currently running"
+
" process instances = 3", responseBody);
}
|
3. Playing with Processes
Now that we have a running process in Activiti
using Spring Boot let's extend the above example to demonstrate how we can
access and manipulate the process.
3.1. Get the List of Tasks for a
Given ProcessInstance
We have two user tasks A and B.
When we start a process, it'll wait for the first task A to be
completed and then will execute task B. Let's create a handler
method that accepts requests to view the tasks related to a given processInstance.
The objects, like Task, cannot be sent
as a response directly and hence we need to create a custom object and convert
the Task to our custom object. We'll call this class TaskRepresentation:
1
2
3
4
5
6
7
|
class TaskRepresentation
{
private String id;
private String name;
private String
processInstanceId;
//
standard constructors
}
|
The handler method will look like:
1
2
3
4
5
6
7
8
9
10
11
12
13
|
@GetMapping("/get-tasks/{processInstanceId}")
public List<TaskRepresentation>
getTasks(
@PathVariable String
processInstanceId) {
List<Task>
usertasks = taskService.createTaskQuery()
.processInstanceId(processInstanceId)
.list();
return usertasks.stream()
.map(task
-> new TaskRepresentation(
task.getId(),
task.getName(), task.getProcessInstanceId()))
.collect(Collectors.toList());
}
|
Here, taskService.createTaskQuery().processInstanceId(processInstanceId).list() uses TaskService and
gets us the list of tasks related to the given processInstanceId.
We can see that when we start running the process we created, we will get the
task A by making a request to the method we just defined:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
|
@Test
public void givenProcess_whenProcessInstance_thenReceivedRunningTask()
throws Exception {
this.mockMvc.perform(MockMvcRequestBuilders.get("/start-process"))
.andReturn()
.getResponse();
ProcessInstance
pi = runtimeService.createProcessInstanceQuery()
.orderByProcessInstanceId()
.desc()
.list()
.get(0);
String
responseBody = this.mockMvc
.perform(MockMvcRequestBuilders.get("/get-tasks/" + pi.getId()))
.andReturn()
.getResponse()
.getContentAsString();
ObjectMapper
mapper = new ObjectMapper();
List<TaskRepresentation>
tasks = Arrays.asList(mapper
.readValue(responseBody,
TaskRepresentation[].class));
assertEquals(1,
tasks.size());
assertEquals("A",
tasks.get(0).getName());
}
|
3.2. Completing a Task
Now, we will see what happens when we complete
task A. We create a handler method that will handle requests to
complete the task A for the given processInstance:
1
2
3
4
5
6
7
|
@GetMapping("/complete-task-A/{processInstanceId}")
public void completeTaskA(@PathVariable String
processInstanceId) {
Task
task = taskService.createTaskQuery()
.processInstanceId(processInstanceId)
.singleResult();
taskService.complete(task.getId());
}
|
taskService.createTaskQuery().processInstanceId(processInstanceId).singleResult() creates a query on the task service and gives
us the task of the given processInstance. This is the UserTask
A. The next line taskService.complete(task.getId) completes
this task.
Hence, now the process has reached the end and the RuntimeService doesn't contain any ProcessInstances. We can see this using the JUnit test case:
Hence, now the process has reached the end and the RuntimeService doesn't contain any ProcessInstances. We can see this using the JUnit test case:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
@Test
public void givenProcess_whenCompleteTaskA_thenNoProcessInstance()
throws Exception {
this.mockMvc.perform(MockMvcRequestBuilders.get("/start-process"))
.andReturn()
.getResponse();
ProcessInstance
pi = runtimeService.createProcessInstanceQuery()
.orderByProcessInstanceId()
.desc()
.list()
.get(0);
this.mockMvc.perform(MockMvcRequestBuilders.get("/complete-task-A/" + pi.getId()))
.andReturn()
.getResponse()
.getContentAsString();
List<ProcessInstance>
list = runtimeService.createProcessInstanceQuery().list();
assertEquals(0,
list.size());
}
|
This is how we can use Activiti services work with
processes.
4. Conclusion
In this article, we went through the overview of
using the Activiti API with Spring Boot. More information about the
API can be found in the user guide. We also saw how to create a
process and execute various operations on it using Activiti services.
Spring Boot makes it easy to use as we don't need
to worry about creating the database, deploying the processes or creating
the ProcessEngine.
Keep in mind the integration of Activiti with
Spring Boot is still in the experimental phase and it is not yet supported by
Spring Boot 2.
Comments
Post a Comment
Please Share Your Views