Table of Contents
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:
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:
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 for the Activiti Developer Series can be found here.
Before starting with your service task implementation make sure to set up a proper Activiti Extension project.
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.
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.
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')}
In a Spring Bean Method we access process variables in the same way as in a POJO Java Delegate, so read its docs.
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.
Not available when using Spring Bean Methods.
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.
See the POJO Java Delegate docs.