Spring Boot + Activiti Example


In this tutorial, we will demonstrate the Spring Boot + Activiti Example. Activiti is an open-source workflow engine that can execute business processes described in BPMN 2.0. Activiti engine has a core goal to take a process definition comprised of human tasks and service calls and execute those in a certain order.

1. What we’ll build

Here, we will design a BMPN workflow diagram and Spring Boot application that helps to manage the process as given below:

Spring Boot + Activiti Workflow Example

After that, we will create a list of employees and store their details into the database using Spring JPA and by calling Activiti API we assign the task to an employee. An employee will complete their first task and the second task.

2. Technologies Used

Find the list of all technologies used in this application.

  1. Spring Tool Suite 3
  2. JDK 8
  3. Spring Boot 1.5.3.RELEASE
  4. Activiti 5.22.0
  5. H2 (in-memory) Database
  6. Spring JPA

Activiti workflow process engine uses the H2 in-memory database to manage the process id, task id, etc but we can also configure the external database like MySQL, Oracle, etc in the application.properties file.

3. Project Structure

Final project structure of our application in STS ide will look like as follows:

Spring Boot + Activiti Workflow Example

4. Dependencies Required

These following dependencies are required to work with Spring Boot + Activiti workflow process engine. Add the following to your pom.xml.

pom.xml
<properties>
	<java.version>1.8</java.version>
	<activiti.version>5.22.0</activiti.version>
</properties>
<dependencies>
	<dependency>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-web</artifactId>
	</dependency>
	<dependency>
		<groupId>org.activiti</groupId>
		<artifactId>activiti-spring-boot-starter-basic</artifactId>
		<version>${activiti.version}</version>
	</dependency>
	<dependency>
		<groupId>org.activiti</groupId>
		<artifactId>activiti-spring-boot-starter-jpa</artifactId>
		<version>${activiti.version}</version>
	</dependency>
	<dependency>
		<groupId>com.h2database</groupId>
		<artifactId>h2</artifactId>
	</dependency>
</dependencies>

5. BPMN Process Definition

Drop the BPMN 2.0 process definition into the src/main/resources/processes folder. All processes placed here will automatically be deployed (ie. parsed and made to be executable) to the Activiti engine. The process definition file extension can be bpmn20.xml or bpmn like simple-process.bpmn20.xml or simple-process.bpmn.

simple-process.bpmn
<?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="Examples">
  <process id="simple-process" name="Simple Process" isExecutable="true">
    <startEvent id="start" name="Start"></startEvent>
    <userTask id="userTask1" name="User Task 1" activiti:assignee="${employee.name}">
      <documentation>Complete user task 1 first.</documentation>
    </userTask>
    <userTask id="userTask2" name="User Task 2" activiti:assignee="${employee.name}">
      <documentation>Work for the second task.</documentation>
    </userTask>
    <endEvent id="theEnd"></endEvent>
    <sequenceFlow id="flow1" sourceRef="userTask2" targetRef="theEnd"></sequenceFlow>
    <sequenceFlow id="flow3" sourceRef="userTask1" targetRef="userTask2"></sequenceFlow>
    <sequenceFlow id="flow4" sourceRef="start" targetRef="userTask1"></sequenceFlow>
  </process>
</definitions>

The activiti:assignee attribute and ${employee.name} expression are used for assigning the task to an employee.

6. Model and Repository Class

Create an Employee data model class.

Employee.java
package org.websparrow.activiti.model;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;

@Entity
public class Employee {

	@Id
	@GeneratedValue
	private Long id;
	private String name;
	private String designation;
// generate getters and setters...

	public Employee() {
	}

	public Employee(String name, String designation) {

		this.name = name;
		this.designation = designation;
	}
}

Create EmployeeRepository interface which extends the JpaRepository<T, ID> interface and define a method findByName(String name) of Employee return type to search the employee from the database.

EmployeeRepository.java
package org.websparrow.activiti.repository;

import org.springframework.data.jpa.repository.JpaRepository;
import org.websparrow.activiti.model.Employee;

public interface EmployeeRepository extends JpaRepository<Employee, Long> {

	public Employee findByName(String name);

}

7. Service Class

The EmployeeService class is responsible for adding employees into the database. This service class will be called when the application is started.

EmployeeService.java
package org.websparrow.activiti.service;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.websparrow.activiti.model.Employee;
import org.websparrow.activiti.repository.EmployeeRepository;

@Service
public class EmployeeService {

	@Autowired
	private EmployeeRepository employeeRepository;

	// create the list of Employees into the database who perform the task
	public void createEmployee() {

		if (employeeRepository.findAll().size() == 0) {

			employeeRepository.save(new Employee("Prince", "Software Enginner"));
			employeeRepository.save(new Employee("Gaurav", "Technical Lead"));
			employeeRepository.save(new Employee("Abhinav", "Test Lead"));
		}
	}
}

And the ProcessService class is responsible for starting the process, assigning a task to an employee, retrieving the tasks assigned to an employee, and completing a particular task by id.

ProcessService.java
package org.websparrow.activiti.service;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.activiti.engine.RepositoryService;
import org.activiti.engine.RuntimeService;
import org.activiti.engine.TaskService;
import org.activiti.engine.task.Task;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.websparrow.activiti.model.Employee;
import org.websparrow.activiti.repository.EmployeeRepository;

@Service
public class ProcessService {

	@Autowired
	private EmployeeRepository employeeRepository;

	@Autowired
	private RuntimeService runtimeService;

	@Autowired
	private TaskService taskService;

	@Autowired
	private RepositoryService repositoryService;

	// start the process and set employee as variable
	public String startTheProcess(String assignee) {

		Employee employee = employeeRepository.findByName(assignee);

		Map<String, Object> variables = new HashMap<>();
		variables.put("employee", employee);

		runtimeService.startProcessInstanceByKey("simple-process", variables);

		return processInformation();
	}

	// fetching process and task information
	public String processInformation() {

		List<Task> taskList = taskService.createTaskQuery().orderByTaskCreateTime().asc().list();

		StringBuilder processAndTaskInfo = new StringBuilder();
		
		processAndTaskInfo.append("Number of process definition available: "
				+ repositoryService.createProcessDefinitionQuery().count() + " | Task Details= ");

		taskList.forEach(task -> {

			processAndTaskInfo.append("ID: " + task.getId() + ", Name: " + task.getName() + ", Assignee: "
					+ task.getAssignee() + ", Description: " + task.getDescription());
		});

		return processAndTaskInfo.toString();
	}

	// fetch task assigned to employee
	public List<Task> getTasks(String assignee) {
		return taskService.createTaskQuery().taskAssignee(assignee).list();
	}

	// complete the task
	public void completeTask(String taskId) {
		taskService.complete(taskId);
	}
}

8. Controller Class

The ProcessController class handles the HTTP request, call the respective ProcessService class method, and response the specific result.

ProcessController.java
package org.websparrow.activiti.controller;

import java.util.List;

import org.activiti.engine.task.Task;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.websparrow.activiti.service.ProcessService;

@RestController
public class ProcessController {

	@Autowired
	private ProcessService processService;

	/*
	 * Method will start the Activiti process engine and set employee to perform
	 * the task
	 */
	@RequestMapping(value = "/process")
	public String startProcessInstance(@RequestParam String assignee) {
		return processService.startTheProcess(assignee);
	}

	// Retrieve the tasks assigned to an employee
	@RequestMapping(value = "/tasks")
	public String getTasks(@RequestParam String assignee) {
		List<Task> tasks = processService.getTasks(assignee);
		return tasks.toString();
	}

	// Complete the task by their ID
	@RequestMapping(value = "/completetask")
	public String completeTask(@RequestParam String taskId) {
		processService.completeTask(taskId);
		return "Task with id " + taskId + " has been completed!";
	}
}

9. Run the application

Finally, create an App class which calls EmployeeSerice‘s createEmployee() method for creating employees while starting the application.

App.java
package org.websparrow.activiti;

import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.websparrow.activiti.service.EmployeeService;

@SpringBootApplication
public class App {

	public static void main(String[] args) {
		SpringApplication.run(App.class, args);
	}

	@Bean
	public CommandLineRunner init(final EmployeeService employeeService) {

		return new CommandLineRunner() {
			public void run(String... strings) throws Exception {
				employeeService.createEmployee();
			}
		};
	}
}

To secure your application with user id and password, add the following code in the App class.

@Bean
InitializingBean usersAndGroupsInitializer(final IdentityService identityService) {

	return new InitializingBean() {
		public void afterPropertiesSet() throws Exception {

			Group group = identityService.newGroup("user");
			group.setName("users");
			group.setType("security-role");
			identityService.saveGroup(group);

			User admin = identityService.newUser("admin");
			admin.setPassword("admin");
			identityService.saveUser(admin);
		}
	};
}

10. Test the application

Now it’s time to test the application. To do that build your application using Maven command clean install and run App class via Spring Boot App. After that following the below steps:

Step 1: Assign a task to an employee (Prince)

http://localhost:8080/process?assignee=Prince

Number of process definition available: 1 | Task Details= ID: 9, Name: User Task 1, Assignee: Prince, Description: Complete user task 1 first.

Step 2: Show the task assigned to Prince

http://localhost:8080/tasks?assignee=Prince

[Task[id=9, name=User Task 1]]

Step 3: Complete the task assigned to Prince by task id.

http://localhost:8080/completetask?taskId=9

Task with id 9 has been completed.

Step 4: Again check  the task assigned to Prince

http://localhost:8080/tasks?assignee=Prince

[Task[id=12, name=User Task 2]]

Step 5: Complete the task again

http://localhost:8080/completetask?taskId=12

Task with id 12 has been completed.

Finally, Prince has been completed all tasks assigned to him, if you Step 2 again, it will return blank array [ ], it means there is no pending task in Prince bucket.

References

  1. Activiti User Guide
  2. Github- Activiti
  3. Getting started with Activiti and Spring Boot

Similar Posts

About the Author

Websparrow
I like sharing my experiments and ideas with everyone by writing articles on the latest technological trends.