Multi Instance Process in Camunda

Reading Time: 10 minutes

As a developer, there is a 90% chance that the client or the business will come to you one day and say “look we are doing this the old way, but we want software now for this”. And there is a big chance that they will have some procedure with a lot of if-else scenarios. There also might be an option for a part of the procedure to be repeated for every customer that they have which means you will have to execute the same logic multiple times. If that happens, don’t be scared, Camunda is here to help and I hope that this blog will help you. We are going to dive into Camunda processes and how we can handle a part of it being repeated multiple times. In order to follow it may be some basic Camunda knowledge, Java and Spring Boot can be of help.

Let’s look at the following scenario:
We have a bank and in it, there is one guy responsible for the whole process of giving loans to people. He is sitting there and a guy John comes and he wants to get a loan. So the bank guy first gives an application to John. Once John is done with the application and returns it to the bank guy, the bank guy requests some documents from his place of work so they can be sure that he is employed full-time. Once that is done, the bank guy needs to send this to corporate so that they can make the final decision.

If we make a diagram of it, it will look something like this:

This is great and everything if you have one customer, but if you have around 100 customers per day the bank guy will have no clue which guy gave him the work information or filled the application.

So let’s make software for this guy and make his life easier, let’s open our Intellij and write some code!

I have already created the project (you can download it from here) and will go through some of the more important things.

First, let’s take a look at the pom file:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.6.1</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>north.com</groupId>
    <artifactId>multi-process-instance</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>multi-process-instance</name>
    <description>multi-process-instance</description>
    <properties>
        <java.version>11</java.version>
    </properties>
    <dependencies>
        <!-- Spring -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>

        <!-- Camunda -->
        <dependency>
            <groupId>org.camunda.bpm.springboot</groupId>
            <artifactId>camunda-bpm-spring-boot-starter-rest</artifactId>
            <version>7.14.0</version>
        </dependency>
        <dependency>
            <groupId>org.camunda.bpm.springboot</groupId>
            <artifactId>camunda-bpm-spring-boot-starter-webapp</artifactId>
            <version>7.14.0</version>
        </dependency>
        <dependency>
            <groupId>org.camunda.bpm</groupId>
            <artifactId>camunda-engine-plugin-spin</artifactId>
            <version>7.14.0</version>
        </dependency>
        <dependency>
            <groupId>org.camunda.spin</groupId>
            <artifactId>camunda-spin-dataformat-json-jackson</artifactId>
            <version>1.10.0</version>
        </dependency>

        <!-- Database -->
        <dependency>
            <groupId>com.h2database</groupId>
            <artifactId>h2</artifactId>
            <scope>runtime</scope>
        </dependency>
        <!-- Lombok -->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

I have added the camunda dependencies because we are going to create camunda processes and an h2 database as well. Even though we are not going to write code that will access the database or create some tables, Camunda needs a database to create its own tables.

Next, let’s look at the application.yml

spring:
  datasource:
    driver-class-name: org.h2.Driver
    url: jdbc:h2:mem:testdb
    username: sa
  jpa:
    database-platform: org.hibernate.dialect.H2Dialect
    hibernate.ddl-auto: update

camunda:
  bpm:
    authorization:
      enabled: false
    default-serialization-format: application/json
    admin-user:
      id: admin
    database:
      type: h2
    generic-properties.properties:
      telemetry-reporter-activate: false
    deployment-resource-pattern:
      - classpath*:processes/*.bpmn

Above we are setting some database properties and some Camunda properties. Mainly we are enabling the admin-user for Camunda to open their cockpit with user admin and we are saying where the processes will be and what kind of database will Camunda use.

Now, having said all that, let’s create the whole scenario as a process in Camunda. First thing you will need is a Camunda modeller which you can download from here. Once you have the modeller up and running go to File -> New File -> BPMN Diagram. What we need to build (or you can just open the existing bpmn diagram from the application) is something that will look like this:

As a first step, we will generate some credit loaners (we are doing this just to have some data to play with) and then set this collection of credit loaners as a value for a variable called creditLoanerList. This will be achieved by using a service task. In this scenario, if you click on the Generate Credit Loaners you will see that we are providing as implementation: Java class and as a java class we are pointing to: north.com.multiprocessinstance.camunda.task.GenerateCreditLoanersTask.java:

package north.com.multiprocessinstance.camunda.task;

import north.com.multiprocessinstance.entity.CreditLoaner;
import org.camunda.bpm.engine.delegate.DelegateExecution;
import org.camunda.bpm.engine.delegate.JavaDelegate;

import java.util.Arrays;
import java.util.List;

public class GenerateCreditLoanersTask implements JavaDelegate {
    @Override
    public void execute(DelegateExecution delegateExecution) throws Exception {
        List<CreditLoaner> creditLoaners = Arrays.asList(new CreditLoaner("Sarah", "Cox"),
                new CreditLoaner("John", "Doe"),
                new CreditLoaner("Filip", "Trajkovski"));
        delegateExecution.setVariable("creditLoanerList", creditLoaners);
    }
}

If you click on the subprocess (it is the largest rectangle with the three dashes) you will see the reason why we set the list of credit loaners as a variable value to creditLoanerList:

In order to pass all the credit loaners and for each of them a subprocess to be started we need to provide a collection of credit loaners. That is why we are setting the Collection field to: creditLoanerList and in order for every credit loaner to be accessed in the appropriate subprocess we are setting an Element Variable that we will call: creditLoaner. What will happen is that we will have this process once for Filip, once for John and once for Sarah.

Let’s look at the tasks in our subprocess. The first one Fulfill Application is a user task and in it, we want the credit loaner to fill out an application. For the next step Get work recommendation to be completed, the credit loaner needs to provide some documents that he is employed and has a regular salary (this is not completely implemented, but we are describing a possible scenario and currently only form fields of type string are defined). In the third step, a service task will be executed and in it, the NotifyCorporate.java class will be executed:

package north.com.multiprocessinstance.camunda.task;

import lombok.extern.slf4j.Slf4j;
import north.com.multiprocessinstance.entity.CreditLoaner;
import org.camunda.bpm.engine.delegate.DelegateExecution;
import org.camunda.bpm.engine.delegate.JavaDelegate;

@Slf4j
public class NotifyCorporate implements JavaDelegate {
    @Override
    public void execute(DelegateExecution delegateExecution) throws Exception {
        final CreditLoaner creditLoaner = (CreditLoaner) delegateExecution.getVariable("creditLoaner");
        log.info("Notify corporate that person {} wants to get credit", creditLoaner.getFirstName());
        delegateExecution.setVariable("creditLoanerFirstName", creditLoaner.getFirstName());
        log.info("This is the user: {}", creditLoaner);
    }
}

Here we will notify the corporate for the credit loaner and to get the information about the credit loaner we will use the variable that was previously set as a Element Variable which is creditLoaner (look at the image of the subprocess details). One additional thing that we will do here is to set one more variable called creditLoanerFirstName and the reason for that will be revealed when we take a look at the next task: Wait for feedback from corporate.

In this Wait for feedback from corporate step one option is that the bank guy got a mail from corporate that the credit loaner is ok and he can get his loan so the bank guy will complete this step and will continue with the next one. For the other option, we are providing a boundary event where the corporate will send a message STOP and for that credit loaner, the request will be rejected. But in our scenario, we have three subprocesses, one for each credit loaner. When sending this STOP message, we need to somehow specify for which credit loaner it is sent. That’s why in the previous step we set the creditLoanerFirstName variable (in real cases don’t use the first name but something like email or other unique value) and one last thing that we will need to do is to set an input parameter in the Wait for feedback from corporate task.

In the Camunda modeller if you click on the task with the name: Wait for feedback from corporate and go to the tab Input/Output you should be able to see the input parameter which will be of type text and the value will be the variable that we set previously, called creditLoanerFirstName.

This input parameter provides us with an option to specify for which credit loaner we want to STOP the procedure and decline his loan request.

But let’s start our application and then start our bank process. Once you have started the application you can open the Camunda cockpit on the following URL: http://localhost:8080/camunda/app/tasklist/default/. The user name and password are admin and admin. Once you have entered the credentials you should be able to start a process from the top right by clicking on Start Process and the BankProcess will be provided (previously we needed to be sure that the BPMN file is saved under resources/processes in your application, since we define in our application.yaml that this will be the place where we will keep the processes).

Let’s take a look at the Camunda cockpit:

Camunda cockpit

Once you started the process you should be able to see the task list (if for some reason they are not visible just click on Add simple filter and they should be displayed). If you click on one of the tasks, tasks details will be displayed and from here we are able to complete it or to open the process details:

Here we have a visual presentation at which step the process is currently. Let’s complete all three tasks Fullfill application by claiming them and completing them and let’s do the same for Get work recommendation. Now we are at the step Wait for feedback from corporate and we can test the boundary event STOP that we added.

There is already an exposed post endpoint in the ProcessController for which we need to provide the process instance id and a MessageEventRequest. In this MessageEventRequest we need to provide the message name, which in our scenario is STOP and a list of correlation keys. The correlation key is where we will specify the credit loaner whose loan request we want to decline. The key will be set to creditLoanerFirstName and let’s decline Sarah’s request (before executing it, make sure that there is a task: Wait for feedback from corporate for Sarah) so we will set the value to Sarah. What we need to do is execute a post request towards: localhost:8080/{processInstanceId}/messageEvent with the following request body:

{
    "messageName":"STOP",
    "correlationKeys":[{
        "key":"     ",
        "value":"Sarah"
    }]
}

As I mentioned previously you can figure out the process instance id and monitor the whole flow of the task using the camunda cockpit that will be available on the following URL: http://localhost:8080/camunda/app/tasklist/default/.

So we started a whole process, started three subprocesses, finished a service task and a user task. I hope that you enjoyed it and also you can find the code on the following link.

Automate Processes with Camunda

Reading Time: 5 minutes

Overview

Camunda BPM is a light-weight, open-source platform for Business Process Management. It ships with tools for creating workflow and decision models, operating deployed models in production, and allowing users to execute workflow tasks assigned to them. It is developed in Java and released as open-source software under the terms of Apache License.

Modeling your first process

In order to show how Camunda works and looks like I will use this simple process. Let us imagine that you want to introduce a review process on your Twitter account and have every tweet go through this review process.

One way to manage this is to make a web application from scratch for this scenario. But we can also model this process with Camunda Modeler and utilize Camunda for this workflow.

On the following image, it is shown one way to model this process with standard BPMN model using Camunda Modeler:

Business Process Model and Notation (BPMN) for the above process

In this diagram, the process is started when someone writes a new tweet. After that, we have a human task where someone has to review this tweet and decide its approval status. And after that we have two possible options if the tweet is approved, a service task is called that will publish this on Twitter. If rejected we again call a service task, however this time we notify the user that his tweet was rejected.

I will go through all of these steps in more detail.

Starting the process

Camunda processes can be started programmatically using some of their supported SDKs like Java or by using the Camunda Tasklist GUI that comes out of the box. In this case, I will use the Camunda tasklist to start a new tweet.

Working on the human task

Human tasks are tasks that must be manually completed by some users. And this can be something as simple as completing a form or it can be something like actually shipping an item somewhere. They are visible in the Camunda Tasklist GUI and users can assign a certain task to themselves and complete them.

In our Camunda BPMN model, the next step in the process is a human task. In our process, we want to review the tweet in this task. On the following image is shown how the human tasks look like by default in Camunda Tasklist:

Automating service tasks

Service task is used to invoke some service, this can be some Java code or some asynchronous external worker.

After the tweet is reviewed we have ‘conditional flow’ in Camunda, which depends on whatever the tweet was approved or not, decides how the flow should continue. In both cases, our flow continues with a service task.

In our case, we have two separate service tasks. One is called when a tweet is rejected and will send a notification, while the other one is used when the tweet is approved and will publish it on Twitter.

First, let us take a look at the service tasks for sending rejection notification:

@Slf4j
@Service("emailAdapter")
public class RejectionNotificationDelegate implements JavaDelegate {

  public void execute(DelegateExecution execution) throws Exception {
    String content = (String) execution.getVariable("content");
    String comments = (String) execution.getVariable("comments");

    log.info("Hi!\n\n"
           + "Unfortunately your tweet has been rejected.\n\n"
           + "Original content: {}\n\n"
           + "Comment: {}\n\n"
           + "Sorry, please try with better content the next time :-)", content, comments);
  }
}

In this code, we obtain process variables like tweet content and rejection comments and we log them in the console. This logic, of course, can be extended to send actual emails, the important thing here is that in order to model Camunda service we only need to implement JavaDelegate interface and override execute method.

In the next code snippet, we have the snippet for publishing the tweet:

public class TweetContentDelegate implements JavaDelegate {

  public void execute(DelegateExecution execution) throws Exception {
	    String content = (String) execution.getVariable("content");

	    AccessToken accessToken = new AccessToken("token", "secret");
	    Twitter twitter = new TwitterFactory().getInstance();
	    twitter.setOAuthConsumer("consumer");
	    twitter.setOAuthAccessToken(accessToken);

	    twitter.updateStatus(content);
	}
}

As in the previous code, we also have to implement JavaDelegate and override execute method.

More Camunda examples can be found on their official GitHub repository: https://github.com/camunda/camunda-bpm-examples

Conclusion

In the above diagram, we have only seen one example of a process, but Camunda offers a lot more features for modeling business processes and a lot of out-of-the-box implementations that save a lot of time. Also, almost everything is customizable.

If your company has a lot of processes that can be modeled as a BPMN process or processes that require human intervention then Camunda can be the right tool for the job.

In my opinion, it’s definitely worth to have a basic understanding of how Camunda works in order to be able to spot a use-case for this tool.