gravitonian

Activiti Enterprise Developer Series - Service Tasks - Spring Bean Methods

Blog Post created by gravitonian Employee on Nov 15, 2016

Table of Contents                   

 

Introduction

Service tasks are one of the fundamental building blocks of any process. They allow you to implement complex business logic, make calculations, talk to external systems and services, and more. In Activiti there are a number of ways in which a service task can be implemented:

 

  1. As a POJO, which is called a Java Delegate
  2. As a Spring Bean Java Delegate
  3. As a Spring Bean method (what this article covers)

 

What implementation approach you choose depend on the use-case. If you don’t need to use any Spring beans in your implementation then use a POJO Java Delegate. If your service task implementation needs to use, for example out-of-the-box Spring beans, then use the Spring Bean Java Delegate. These two approaches uses a “one operation per service task” implementation. If you need your implementation to support multiple operations, then go with the Spring bean method implementation.

 

There is also a runtime twist to this, most likely there will be multiple process instances calling the same service task implementation. And the same service task implementation might be called from multiple service tasks in a process. The implementation behaves differently depending on approach:

 

  1. POJO Java Delegate - inside a process instance you have one class instance per service task, between process instances you share class instances per service task
  2. Spring Bean Java Delegate - same Spring bean (i.e. class instance) is used within a process instance and between process instances
  3. Spring Bean method - same Spring bean (i.e. class instance) is used within a process instance and between process instances but there is no field injection and all data is passed in via method params, so thread-safe

 

What this basically means is that if you use a third party class inside a Java Delegate, then it needs to be thread safe as it can be called by multiple concurrent threads. If you use a Spring bean Java Delegate approach then the same thing applies, if you inject beans they need to all be thread safe. With the Spring bean approach you can also change the bean instantiation scope to be PROTOTYPE, which means an instance will be created per service task.

 

Or you can use the Spring Bean Method approach, which this article covers, and which solves a lot of concurrency issues.

Source Code

Source code for the Activiti Developer Series can be found here.

Prerequisites

Before starting with your service task implementation make sure to set up a proper Activiti Extension project.

Implementing a Hello World Spring Bean Method

Let’s start the usual way with a Hello World Spring Bean Method. This differs from the other approaches as we are not implementing one specific execute method for a Java Delegate. Here we are implementing as many methods as we want, serving as many service tasks as we need from the same Spring Bean implementation.

Coding the Spring Bean class

Here is the implementation of the class:

 

package com.activiti.extension.bean.service;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;


@Service
public class HelloWorldService {
  private static Logger logger = LoggerFactory.getLogger(HelloWorldService.class);

  public String greeting() {
      return "Hello World from Service!";
  }

  public void customGreeting(String text) {
      logger.info("[Java object=" + this + "]");
      logger.info("Hello World: " + text);
  }
}

 

So every method we provide inside the Spring Bean class implementation is a potential Service Task implementation. In this case we are going to use the customGreeting method as the implementation and pass in different texts depending on what service task that is calling the method.

 

This is completely thread safe as we are not using any class members, just passing in data via a method parameter. However, the logging library needs to be thread safe as this method can be called from multiple concurrent threads, serving different service tasks.

Testing the method

Now to test the Spring Bean method as a service task implementation create a process model looking like this:

 

 

And the Service Task is connected to the Spring Bean method implementation via the Expression property:

 

 

The first service task has the following Expression:

 

${helloWorldService.customGreeting('Service Task 1')}

 

And the second task has the following Expression:

 

${helloWorldService.customGreeting('Service Task 2')}

 

In BPMN 2.0 XML it will look like this:

 

<serviceTask id="serviceTask1"
             name="Service Task 1 (Spring Bean method customGreeting(x))"
             activiti:expression="${helloWorldService.customGreeting('Service Task 1')}">

 

When we run this process it will print similar logs to the following:

 

03:11:10,919 [http-nio-8080-exec-7] INFO  com.activiti.extension.bean.service.HelloWorldService  - [Java object=com.activiti.extension.bean.service.HelloWorldService@4ed3dbd3]

03:11:10,919 [http-nio-8080-exec-7] INFO  com.activiti.extension.bean.service.HelloWorldService  - Hello World: Service Task 1

03:11:10,920 [http-nio-8080-exec-7] INFO  com.activiti.extension.bean.service.HelloWorldService  - [Java object=com.activiti.extension.bean.service.HelloWorldService@4ed3dbd3]

03:11:10,920 [http-nio-8080-exec-7] INFO  com.activiti.extension.bean.service.HelloWorldService  - Hello World: Service Task 2

 

We can see here that all the context for the method is passed in as a parameter. In this case it’s only a String of text, but could be any parameters you like. So using the same Spring bean works fine as the logging library is thread safe.

 

Now, what about process information and process variables? How do I get to them? That’s easy, just add an extra parameter of type DelegateExecution:

 

package com.activiti.extension.bean.service;

import org.activiti.engine.delegate.DelegateExecution;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;

@Service
public class HelloWorldService {
  private static Logger logger = LoggerFactory.getLogger(HelloWorldService.class);

  public String greeting() {
      return "Hello World from Service!";
  }

  public void customGreeting(DelegateExecution execution, String text) {
      logger.info("[Process=" + execution.getProcessInstanceId() +
                  "][Java Delegate=" + this + "]");

      logger.info("Hello World: " + text);
  }
}

 

And update the Expression as follows:

 

${helloWorldService.customGreeting(execution, 'Service Task 1')}

Using Process Variables in the Spring Bean Method

In a Spring Bean Method we access process variables in the same way as in a POJO Java Delegate, so read its docs.

Returning data from the Spring Bean Method

There are probably many situations where we would want to return some data from the Spring Bean method and use it in the process. We can achieve this by defining return values for the service task. First update the customGreeting method to return a String:

 

public String customGreeting(DelegateExecution execution, String text) {
  logger.info("[Process=" + execution.getProcessInstanceId() + "][Java Delegate=" + this + "]");
  logger.info("Hello World: " + text);

  return "Something back from service!";
}

 

Now, to return this back to the process instance as a process variable we need to do the following property configuration for each service task:

 

 

Basically we need to specify the process variable name as the Result variable name property.

 

In BPMN 2.0 XML it will look like this:

 

<serviceTask id="serviceTask1"
             name="Service Task 1 (Spring Bean method customGreeting(x))"
             activiti:expression="${helloWorldService.customGreeting(execution, 'Service Task 1')}"
             activiti:resultVariableName="greetingFromService">

 

You could now use the REST API to list the variables available for the process instance after the Service Tasks have been executed, we should see the greetingFromService variable:

 

The URL path parameter 122501 in the above example is the process instance ID.

Using Field Injection in the Spring Bean Method

Not available when using Spring Bean Methods.

Injecting Beans into the backing Spring Bean

When we use Spring we most likely want to use other Spring Beans in our Java Delegate implementation. This can be Spring beans that we create and out-of-the-box Spring beans.

 

This works the same way as for Spring Bean Java Delegates, see docs.

Calling Spring Bean Methods Asynchronously

See the POJO Java Delegate docs.

Outcomes