A small opinionated annotated-based SpringBoot worker implementation for creation of external task workers using Process-Engine-API.
Add the following dependency to your project's class path:
<dependency>
<groupId>dev.bpm-crafters.process-engine-worker</groupId>
<artifactId>process-engine-worker-spring-boot-starter</artifactId>
</dependency>
Provide a Spring Component and annotate its method as following and doing everything else manually:
@Component
@RequiredArgsConstructor
public class MyWorker {
private final FetchGoodsInPort fetchGoodsInPort;
@ProcessEngineWorker(topic = "fetchGoods", autoComplete = false)
public void fetchGoods(
TaskInformation taskInformation,
ExternalTaskCompletionApi externalTaskCompletionApi,
VariableConverter variableConverter,
Map<String, Object> processPayload
) {
var order = variableConverter.mapToType(payload.get("order"), Order.class);
// execute some business code
var fetched = fetchGoodsInPort.fetchGoods(order);
// complete the task using process engine API
externalTaskCompletionApi.completeTask(
new CompleteTaskCmd(taskInformation.getTaskId(), () -> Map.of("shipped", fetched))
).get();
}
}
You can get it even more convenient, if you want the worker to auto complete the task after the execution and use the automatic variable conversion:
@Component
@RequiredArgsConstructor
public class MySmartWorker {
private final FetchGoodsInPort fetchGoodsInPort;
@ProcessEngineWorker(topic = "fetchGoods")
public Map<String, Object> fetchGoods(
@Variable(name = "order") Order order
) {
// execute some business code
var fetched = fetchGoodsInPort.fetchGoods(order);
return Map.of("shipped", fetched);
}
}
Parameter resolution of the method annotated with ProcessEngineWorker
is based on a set of strategies
registered by the ParameterResolver
bean. Currently, the following parameters are resolved:
Type | Purpose |
---|---|
TaskInformation | Helper abstracting all information about the external task. |
ExternTaskCompletionApi | API for completing the external task manually |
VariableConverter | Special utility to read the process variable map and deliver typed value |
Map<String, Object> | Payload object containing all variables. |
Type annotated with @Variable("name) | Marker for a process variable. |
Usually, the requested variable is mandatory and the parameter resolver reports an error, if the requested variable is not
available in the process payload. If you want to inject the variable only if it exists in the payload you have two options.
Either you set the parameter @Variable(name = "...", mandatory = false)
or you use Optional<T>
instead of T
as a variable
type. If you are using Kotlin and don't like Optional
, make sure to declare variable type as nullable (T?
instead of T
) and
set the mandatory flag to false
.
If the return type of the method is of type Map<String, Object>
or compatible and the autoComplete
flag is turned
on the annotation is true
(defaults to true
), the library will try to automatically complete the External Task
using the returned map as completion variables. If autoComplete
is true
, but no return value is provided, the task
will be completed without empty payload. This functionality is provided by the ResultResolver
based on registered strategies.
If you want to throw a BPMN error, please throw an instance of a BPMNErrorOccured
.
You might want to register your own parameter resolution strategies. For this purpose, please construct the parameter resolver bean on your own and register your own strategies:
@Configuration
class MyConfig {
@Bean
fun myParameterResolver(): ParameterResolver {
return ParameterResolver.builder().addStrategy(
ParameterResolutionStrategy(
parameterMatcher = { param -> ... },
parameterExtractor = { param, taskInformation, payload, variableConverter, taskCompletionApi -> ... },
),
).build()
}
}
Optionally, you might want to register own result resolution strategies. For this purpose, please construct the result resolver bean on your own and register your own strategies:
@Configuration
class MyConfig {
@Bean
fun myResultResolver(): ResultResolver {
return ResultResolver.builder().addStrategy(
ResultResolutionStrategy(
resultMatcher = { method -> ... },
resultConverter = { result -> ... },
),
).build()
}
}
If you want to switch the entire library off (for example you are in the context of an integrtion test, and parts of your Process Engine API are deactivated),
you can do it by setting the property dev.bpm-crafters.process-api.worker.enabled
to false
.
There is an Order fulfillment
example, you can easily try out. It follows the approach of
clean architecture and uses Process Engine API and Process Engine Worker libraries.
To run it, you have several options:
- Start
FulfillmentProcessApplication
activating profilec7embedded
Spring profile.
- Start
docker-compose.yaml
(this will start containerized Zeebe locally) - Start
FulfillmentProcessApplication
activating Spring profilec8sm
.
- Start
FulfillmentProcessApplication
activating Spring profilec8cloud
and pass the following environment variables:
ZEEBE_REGION=..
ZEEBE_CLUSTER_ID=..
ZEEBE_CLIENT_ID=..
ZEEBE_CLIENT_SECRET=...
After starting application, you can either use Open API endpoints or just run the HTTP client tests using your IntelliJ, located in the example directory.
Don't forget to first deploy the process! Either manually via operate / modeler, or with the HTTP client script: c8-deploy-process.http