About using Activiti BPM in business process development
Hello everyone! We mentioned earlier that the Unidata platform is actively working with business processes and supports BPMN notation at the heart of their design. For BP development, we use the open source Activiti BPMN engine based on java. Among the available open source products for business process design, we chose Activiti for 2 reasons:
We already had experience with this product
There is a convenient Apache license for commercial development
In this article, we will share our own experience of using Activiti in the development of real business processes for a customer.
The first step in developing a business process is to form a business process diagram. For example, the Activiti plug-in for Eclipse allows you to visually design a business process in BPMN 2.0 notation. The visual editor has all the necessary components for this: tasks of various types, subprocesses, gateways, triggers, scripts, and others. It is enough to drag a BPM element from the list onto the diagram, and then connect all the elements with “arrows”, as in BPMN notation it is denoted “Sequence Flow”. The diagram of the business process by means of Activiti is written into a special configuration xml-file, which can be further edited. Below is an example of a simple business process.
Brief information about what the BPMN elements are for, presented in the diagram:
UserTask is the main task performed by the user. Business process step
ServiceTask is used when, when moving from one task to another, it is necessary to perform an automatic action, for example, calculate the lifetime of a task.
MailTask is used to send mail notification after task completes
Sequence Flow is used to create a sequence of execution of processes. For each operation (“Arrows”), the conditions for starting the next step of the process are set
Gateways are responsible for branching or parallelizing a process according to a given condition
Triggers allow you to terminate or redirect a process when a specific event occurs. For example, we often use a time trigger to enforce an SLA for a process.
We will not dwell on how to draw a diagram, as it is quite easy and information can be easily found on the Internet. Instead, we’ll look directly at the Activiti mechanisms that integrate a business process into the Unidata platform. In order for the process to start working, you need to write a java class that implements the WorkflowProcessSupport interface, each method of which is responsible for a specific process event. The WorkflowProcessSupport interface is responsible for interacting with Activiti in the Unidata platform. Below are examples of methods for such an implementation.
@Override
public WorkflowProcessStartState processStart(StartProcessRequestContext ctx) {
WorkflowProcessStartState state = new WorkflowProcessStartState();
state.setAllow(true);
Map<String, Object> processVariables = state.getAdditionalProcessVariables();
if (processVariables == null) {
processVariables = new HashMap<>();
}
processVariables.put("validFrom", ctx.getValidFrom());
processVariables.put("validTo", ctx.getValidTo());
state.setAdditionalProcessVariables(processVariables);
return state;
}
The processStart method is called before starting the task (userTask) within the process. At this stage, the task_id is not yet created, but all process and task parameters are already initiated.
@Override
public WorkflowProcessAfterStartState afterProcessStart(String processInstanceId, String
processDefinitionId, Map<String, Object> variables) {
WorkflowProcessAfterStartState state = new WorkflowProcessAfterStartState();
Map<String, Object> processVariables = state.getAdditionalProcessVariables();
if (processVariables == null) {
processVariables = new HashMap<>();
}
processVariables.put("processId", processInstanceId);
String initiatorEmail = (String) variables.get("initiatorEmail");
if (initiatorEmail == null || !initiatorEmail.contains("@"))
processVariables.put("initiatorEmail", "testmail@testmail.com");
state.setAdditionalProcessVariables(processVariables);
return state;
}
The afterProcessStart method is called after the task starts. Unlike the processStart event, tasl_id already exists at this stage, and we can interact with the task.
@Override
public WorkflowTaskCompleteState complete(String taskDefinitionId, Map<String, Object> variables,
String actionCode) {
WorkflowTaskCompleteState state = new WorkflowTaskCompleteState(true, "FINISHED".equals(actionCode) ? "Approve task." : "Decline task.");
Map<String, Object> additionalProcessVariables = state.getAdditionalProcessVariables();
if (additionalProcessVariables == null) {
additionalProcessVariables = new HashMap<>();
}
Map<String, Object> additionalTaskVariables = state.getAdditionalProcessVariables();
if (taskDefinitionId.equals("expertTask")) {
if (!WorkflowUtils.curatorsIsAvailable("role_name")) {
throw new WorkflowException(USER_NOT_AVALIBLE, ExceptionId.EX_WF_CANNOT_COMPLETE_TASK_COMPLETION_ERROR);
}
}
state.setAdditionalProcessVariables(additionalProcessVariables);
state.setAdditionalTaskVariables(additionalTaskVariables);
return state;
}
The complete method is called after the completion of the task, that is, the transition from the current task to the next is carried out according to the process diagram.
@Override
public WorkflowProcessEndState processEnd(String processDefinitionId, Map<String, Object> variables) {
String selectorValue = (String) variables.get("finishTaskSelector");
WorkflowProcessEndState state = new WorkflowProcessEndState();
if (selectorValue != null && selectorValue.equals("complete")) {
state.setComplete(true);
} else {
state.setComplete(false);
}
LOGGER.info(String.format("End process %s with action %s", processDefinitionId, selectorValue));
return state;
}
The processEnd method is called after the end of the entire business process. Completion of a business process can be either positive, when the process has successfully completed on all tasks, or negative, when the process can be interrupted for any task.
In order to describe the logic in the indicated methods, special task and process variables are used. These special variables are provided by Activiti itself and are set as property values for Activiti components. For example, these can be Form properties for UserTask.
In this example, there is a descriptionCode variable that will store the task description code, and we also have a taskSelector variable that stores a list of available solutions when the task is completed. Depending on the chosen solution, the process will follow the path defined in the diagram. In general, almost every business process has branches that are regulated by gateways. Each gateway accepts a parameter as input, which determines which path the process will follow.
Another important feature to consider is the assignment of a task performer. To do this, in the parameters of the UserTask element, in the Main config section, there are the Assignee and Candidate group parameters. If the Assignee parameter is filled in, the platform assigns the task to a specific user. If the Candidate group parameter is filled, then the task becomes available to all users in the specified group, so users with a certain role will be able to take this task for execution.
In simple cases, it is enough to create a process diagram and create class methods for the business process to work. In the platform, the administrator specifies for which entity the process should be launched. However, in our practice, there are practically no simple business processes. Very often, when moving from task to task, it is necessary to send email notifications, do data checks when a particular task is completed, change process parameters, and others depending on the task. Therefore, in addition to describing standard classes and building a schema, the developer must directly contact Activiti to implement logic specific to a specific task. For this, Activiti provides Listeners, which specify a Java class that describes the logic of a specific task.
In general, all these mechanisms allow us to design any business process and successfully embed it into a finished production solution, but Activiti also has significant drawbacks, the main of which is the organization of data storage in a database. The fact is that in practice we initialize and use a large number of variables both for the tasks of the business process and for the process itself. Activiti does not have a separate table for storing variables; instead, variables are written to a separate field in the main task and process storage tables. Therefore, one process can have more than a hundred lines for each variable. When the number of business processes reaches several hundred thousand or more, the Activiti tables become excessively massive and the standard Activiti search through SQL queries in the database becomes ineffective. To solve this problem, the platform, at each initiation of a process and tasks, as well as after their completion, duplicates Activiti data into elasticsearch indexes. During search queries, we already refer to the elastic indexes and do not touch the base.
In general, Activiti BPM allows you to easily and quickly develop any business processes and embed them into the existing infrastructure, but it should be borne in mind that there are certain problems with storing data in the database.