This post shows an example of using the Camunda Workflow API. It can be a handy and easy-to-use interface, if you want to dive into your workflow environment a little deeper.
In a recent client project, we were confronted with the need to dynamically switch on and off a timer event, triggering email reminders for the user. The interesting part was that this should also happen for other process instances of the same user. Since a single process instance did not know any of the other „siblings“, the Camunda Workflow API came in handy.
To help you understand, we want to show you a model of the facilitated business use case (Image 1).
Imagine we want to lease a car. Let“™s say we are interested in 3 different manufacturers so we configure 3 different cars and start one leasing request process for each one of these cars.
After our leasing company calculated the monthly rates, we want to be able to select the car with the most reasonable rate. The 3 processes will remind us to do so, in case we wait too long.
So far so good. Imagine now, we choose one rate out of our 3 inquiries and the relevant process instance will make sure next step „check credit-worthiness“ is triggered. Besides that, the two other instances remain at the point to choose the rate. So while we wait and hope that our credit-worthiness goes through, we still get emails to remind us checking the rates for cars, we aren’t interested in any more…
Why don’t we just delete the other instances? Good question: We had the requirement, that the specific rate is part of the credit-worthiness check. In case it fails, the customer might want to order another car (hence instances are still ticking at this point). But yes, to avoid instance-zombies after a car was successfully ordered, the remaining instances will be deleted at the end of the process.
This requirement resulted in the following considerations. To stop reminder emails, we have to filter active timer events and suspend the resultset. To do so, we need three types of information.
- CustomerId (to get all instances for)
- processDefinitionKey (to distinguish from instances of other processes)
- BoundaryEventName (to request the right subset)
This is the code, we came up with:
[code language=“java“]
public void suspendTimerEventInOtherProcessInstances(String processDefinitionKey,
Long customerId, String boundaryEventName) {
List
getTimersForProcessByCustomerIdAndJobName(processDefinitionKey, customerId, boundaryEventName);
for (Job job : timerJobs) {
managementService.suspendJobById(job.getId());
}
}
private List
Long customerId, String boundaryEventName) {
List
runtimeService.createProcessInstanceQuery().processDefinitionKey(processDefinitionKey)
.variableValueEquals(ProcessVariables.CUSTOMER_ID, customerId).list();
List
for (ProcessInstance processInstance : processInstanceList) {
List
managementService.createJobQuery().processInstanceId(processInstance.getId()).timers()
.active().list();
for (Job job : timerJobs) {
if (((org.camunda.bpm.engine.impl.persistence.entity.TimerEntity) job)
.getJobHandlerConfiguration().equals(boundaryEventName)) {
jobList.add(job);
}
}
}
return jobList;
[/code]
You will notice that Camundas RuntimeService enables us to query ProcessInstances by processDefinitionKey and ProcessVariables (CustomerId). That gave us a list of matching process instances. So far so good.
We now used this list to extract every active timer job of each instance, using Camundas ManagementService. To ensure to exclusively hold the requested timers in hand (one process instance might include further timers), we needed to compare each timer jobs „name“ with the given boundaryEventName. The necessary information can be found in field JobHandlerConfiguration, made available by casting the job to TimerEntity, supplying a matching getter (images 2 and 3).
OK – after we got hands on the requested TimerJobs, we suspended them.
The second step, after a car was ordered was to delete the other, now unimportant, instances:
To delete all the other process instances of cars we aren“™t interested anymore, we called this piece of code in the end of a successful process:
[code language=“java“]
public void deleteAllOtherMultibidProcessesOfUser(String processDefinitionKey, Long customerId,
String instanceIdToStayAlive){
List
runtimeService.createProcessInstanceQuery().processDefinitionKey(processDefinitionKey)
.variableValueEquals(ProcessVariables.USER_ID, customerId).active().list();
for (ProcessInstance processInstance : processInstanceList) {
if (!instanceIdToStayAlive.equals(processInstance.getId())) {
runtimeService.deleteProcessInstance(processInstance.getId(),
„Instance deleted because user ordered other car.“);
}
}
}
[/code]
Again, we used
- CustomerId (to get all instances for)
- processDefinitionKey (to distinguish from instances of other processes)
while we introduced
- instanceIdToStayAlive.
This seemed to be the easiest way to make sure not to delete the actual calling instance.
As you can see, we use RuntimeService again to query process instances by ProcessDefinitionKey and CustomerId. After that, we only kept alive the one instance, that was still relevant for handling the customers car-leasing.
I hope this post was useful,
Stefan