Process Automation Example – Part 2: Workflows via Activiti

This is the second and final part of the process automation example. After elaborating on basic configuration and testing it is on time I touched the key feature, the workflow management based on Activiti, a light-weight BPMN process engine.

Just a brief refresher, the example is a simple incident tracking system. Details can be found in my previous post. Let’s take a look at how Activiti helps manage the business process automation.

Since I like to minimize direct dependencies on external tools, I have created a custom facade as a single point of communication with Activiti API. Here is the deal:

public interface WorkflowManager {
    /**
     * Triggers the specified process and injects the incident in it.
     *
     * @param incidentId
     * @param processId
     */
    void process(Long incidentId, String processId);

    /**
     * Triggers the specified process, injects the incident and adds
     * extra paramaters.
     *
     * @param incidentId
     * @param processId
     * @param model     any parameters out of incident's reach
     */
    void process(Long incidentId, String processId, Map model);

    /**
     * Completes a pending task.
     *
     * @param name
     * @param assignee
     */
    void completeTask(String name, String assignee);

As you can see my only concern was to be able to start a process and close off a pending task on behalf of the person in charge.

Activiti enables to pass arguments into process definitions. Pretty much any object can be therefore accessed as a variable within the particular process. An incident is the key entity in my domain model so I let it be looked up by the primary key and passed into the process. Under certain circumstances however, other details are needed. This is why the process() method has been overloaded to accept extra parameters as a map of key/value pairs.

The processes are defined by using BPMN 2.0. For instance, the incident confirmation is described as follows:

<?xml version="1.0" encoding="UTF-8"?>
<definitions id="definitions"
             targetNamespace="http://activiti.org/bpmn20"
             xmlns:activiti="http://activiti.org/bpmn"
             xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL">

  <process id="confirmNewIncident"
   name="send an email notification regarding a new incident">
    <startEvent id="newIncidentCaptured"/>

    <sequenceFlow id="toSendNewIncidentMail"
     sourceRef="newIncidentCaptured"
     targetRef="sendNewIncidentMail"/>

    <serviceTask id="sendNewIncidentMail" activiti:type="mail">
      <extensionElements>
        <activiti:field name="to"
         expression="admin@crashtracker.com" />
        <activiti:field name="subject"
         expression="${incident.createdBy}: New Incident Report" />
        <activiti:field name="html"
         expression="${incident.severity.value} Problem:
         ${incident.description}"/>
      </extensionElements>
    </serviceTask>

    <sequenceFlow id="toIncidentConfirmed"
     sourceRef="sendNewIncidentMail"
     targetRef="incidentConfirmed" />

    <endEvent id="incidentConfirmed" />
  </process>
</definitions>

The process has to have a clearly defined start and end. Tasks are chained by sequence flows. Activiti supports various kinds of tasks and sequences to cater for complex workflows and decision making structures. The incident confirmation, however, consists of a single task. The system is supposed to send out an email notification. Notice how the incident properties are used as variables in the email template. The email task is a feature which comes in handy on a number of occasions, I have blogged about it in my previous post.

The process definition can be instantiated only after it has been deployed:

..
import org.activiti.engine.RepositoryService;
..
@Service
public class ActivitiWorkflowManager implements WorkflowManager {
    ..
    @Resource
    private RepositoryService repositoryService;

    private String deploymentId;
    ..

    // Other variables and implementations

    // Loads the workflow definitions so that the processes
    // can be started later on
    private void deploy() {
        deploymentId = repositoryService
                .createDeployment()
                .addClasspathResource(
                 "workflows/confirm_new_incident.bpmn20.xml")
                .deploy().getId();
    }

}

Deployed processes can be easily accessed and triggered:

..
import org.activiti.engine.RuntimeService;
..
    @Resource
    private RuntimeService runtimeService;
    ..
    runtimeService.startProcessInstanceByKey("confirmNewIncident");
    ..

Activiti provides an advanced query API for an easy access to processes and tasks. Closing off a pending task is a matter of knowing whom has it been assigned to:

..
import org.activiti.engine.TaskService;
import org.activiti.engine.task.Task;
..
    ..
    // Imagine that John is in charge of resolving an incident
    Task task = taskService
                  .createTaskQuery()
                  .taskName("resolve the incident")
                  .taskAssignee("John")
                  .singleResult();
    taskService.complete(task.getId());
    ..

There are many subtle details, such as Activiti and Spring support, various kinds of available services (way too many for my taste), intricacies of parameter passing etc. Answers can be found in the Activiti documentation and the attached source code.

This wraps up all I wanted to say about process automation. I have devoted a considerable amount of time to this topic and to me, Activiti is the tool to go with. I am keen to hear about other options and hope someone finds this post useful.

Download Source Code or Explore It

Similar Posts