jBPM for Developers: Part 2

0
93
11 min read

Process execution

At this point, where our definitions are ready, we can create an execution of our defined processes. This can be achieved by creating a class where each instance represents one execution of our process definition—bringing our processes to life and guiding the company with their daily activities; letting us see how our processes are moving from one node to the next one. With this concept of execution, we will gain the power of interaction and influence the process execution by using the methods proposed by this class.

We are going to add all of the methods that we need to represent the executional stage of the process, adding all the data and behavior needed to execute our process definitions.

This process execution will only have a pointer to the current node in the process execution. This will let us query the process status when we want.

jBPM Developer Guide

An important question about this comes to our minds: why do we need to interact with our processes? Why doesn’t the process flow until the end when we start it?

And the answer to these important questions is: it depends. The important thing here is to notice that there will be two main types of nodes:

  • One that runs without external interaction (we can say that is an automatic node). These type of nodes will represent automatic procedures that will run without external interactions.
  • The second type of node is commonly named wait state or event wait. The activity that they represent needs to wait for a human or a system interaction to complete it. This means that the system or the human needs to create/fire an event when the activity is finished, in order to inform the process that it can continue to the next node.

Wait states versus automatic nodes

The difference between them is basically the activity nature. We need to recognize this nature in order to model our processes in the right way. As we have seen before, a “wait state” or an “event wait” situation could occur when we need to wait for some event to take place from the point of view of the process. These events are classified into two wide groups—Asynchronous System Interactions and Human tasks.

Asynchronous System Interactions

This means the situation when the process needs to interact with some other system, but the operation will be executed in some asynchronous way.

For non-advanced developers, the word “asynchronous” could sound ambiguous or without meaning. In this context, we can say that an asynchronous execution will take place when two systems communicate with each other without blocking calls. This is not the common way of execution in our Java applications. When we call a method in Java, the current thread of execution will be blocked while the method code is executed inside the same thread. See the following example:

jBPM Developer Guide

The doBackup() method will block until the backup is finished. When this happens, the call stack will continue with the next line in the main class. This blocking call is commonly named as a synchronous call.

On the other hand, we got the non-blocking calls, where the method is called but we (the application) are not going to wait for the execution to finish, the execution will continue to the next line in the main class without waiting.

In order to achieve this behavior, we need to use another mechanism. One of the most common mechanisms used for this are messages.

Let’s see this concept in the following image:

jBPM Developer Guide

In this case, by using messages for asynchronous executions, the doBackup() method will be transformed into a message that will be taken by another thread (probably an external system) in charge of the real execution of the doBackup() code. The main class here will continue with the next line in the code. It’s important for you to notice that the main thread can end before the external system finishes doing the backup. That’s the expected behavior, because we are delegating the responsibility to execute the backup code in the external system. But wait a minute, how do we know if the doBackup() method execution finished successfully? In such cases, the main thread or any other thread should query the status of the backup to know whether it is ready or not.

Human tasks

Human tasks are also asynchronous, we can see exactly the same behavior that we saw before. However, in this case, the executing thread will be a human being and the message will be represented as a task in the person’s task list.

jBPM Developer Guide

As we can see in this image, a task is created when the Main thread’s execution reaches the doBackup() method. This task goes directly to the corresponding user in the task list. When the user has time or is able to do that task, he/she completes it. In this case, the “Do Backup” activity is a manual task that needs to be performed by a human being.

In both the situations, we have the same asynchronous behavior, but the parties that interact change and this causes the need for different solutions.

For system-to-system interaction, probably, we need to focus on the protocols that the systems use for communication.

In human tasks, on the other hand, the main concern will probably be the user interface that handles the human interaction.

How do we know if a node is a wait state node or an automatic node?
First of all, by the name. If the node represents an activity that is done by humans, it will always wait. In system interactions, it is a little more difficult to deduce this by the name (but, if we see an automatic activity that we know takes a lot of time, that will probably be an asynchronous activity which will behave as a wait state). A common example could be a backup to tape, where the backup action is scheduled in an external system. If we are not sure about the activity nature we need to ask about the activity nature to our stakeholder.

We need to understand these two behaviors in order to know how to implement each node’s executional behavior, which will be related with the specific node functionality.

Creating the execution concept in Java

With this class, we will represent each execution of our process, which means that we could have a lot of instances at the same time running with the same definition. Inside the package called org.jbpm.examples.chapter02.simpleGOP.execution (provided at www.packtpub.com/files/code/5685_Code.zip), we will find the following class:

public class Execution {
private Definition definition;
private Node currentNode;

public Execution(Definition definition) {
this.definition = definition;
//Setting the first Node as the current Node
this.currentNode = definition.getNodes().get(0);
}

public void start(){
// Here we start the flow leaving the currentNode.
currentNode.leave(this);
}
... (Getters and Setters methods)
}

As we can see, this class contains a Definition and a Node, the idea here is to have a currentNode that represents the node inside the definition to which this execution is currently “pointing”. We can say that the currentNode is a pointer to the current node inside a specific definition.

The real magic occurs inside each node. Now each node has the responsibility of deciding whether it must continue the execution to the next node or not. In order to achieve this, we need to add some methods (enter(), execute(), leave()) that will define the internal executional behavior for each node. We do this in the Node class to be sure that all the subclasses of the Node class will inherit the generic way of execution. Of course, we can change this behavior by overwriting the enter(), execute(), and leave() methods.

We can define the Node.java class (which is also found in the chapter02.simpleGOPExecution project in the code bundle) as follows:

...
public void enter(Execution execution){
execution.setCurrentNode(this);
System.out.println("Entering "+this.getName());
execute(execution);
}
public void execute(Execution execution){
System.out.println("Executing "+this.getName());
if(actions.size() > 0){
Collection<Action> actionsToExecute = actions.values();
Iterator<Action> it = actionsToExecute.iterator();
while(it.hasNext()){
it.next().execute();
}
leave(execution);
}else{
leave(execution);
}
}
public void leave(Execution execution){
System.out.println("Leaving "+this.getName());
Collection<Transition> transitions =
getLeavingTransitions().values();
Iterator<Transition> it = transitions.iterator();
if(it.hasNext()){
it.next().take(execution);
}
}
...

As you can see in the Node class, which is the most basic and generic implementation, three methods are defined to specify the executional behavior of one of these nodes in our processes. If you carefully look at these three methods, you will notice that they are chained, meaning that the enter() method will be the first to be called. And at the end, it will call the execute() method, which will call the leave() method depending on the situation. The idea behind these chained methods is to demarcate different phases inside the execution of the node.

All of the subclasses of the Node class will inherit these methods, and with that the executional behavior. Also, all the subclasses could add other phases to demarcate a more complex lifecycle inside each node’s execution.

The next image shows how these phases are executed inside each node.

jBPM Developer Guide

As you can see in the image, the three methods are executed when the execution points to a specific node. Also, it is important to note that transitions also have the Take phase, which will be executed to jump from one node to the next.

All these phases inside the nodes and in the transition will let us hook custom blocks of code to be executed.

One example for what we could use these hooks for is auditing processes. We could add in the enter() method, that is the first method called in each node, a call to an audit system that takes the current timestamp and measures the time that the node uses until it finishes the execution when the leave() method is called.

jBPM Developer Guide

Another important thing to notice in the Node class is the code inside the execute() method. A new concept appears. The Action interface that we see in that loop, represents a pluggable way to include custom specific logic inside a node without changing the node class. This allows us to extend the node functionality without modifying the business process graph. This means that we can add a huge amount of technical details without increasing the complexity of the graph. For example, imagine that in our business process each time we change node, we need to store the data collected from each node in a database. In most of the cases, this requirement is purely technical, and the business users don’t need to know about that. With these actions, we achieve exactly the above. We only need to create a class with the custom logic that implements the Action interface and then adds it to the node in which we want to execute the custom logic.

jBPM Developer Guide

The best way to understand how the execution works is by playing with the code. In the chapter02.simpleGOPExecution maven project, we have another test that shows us the behavior of the execution class. This test is called TestExecution and contains two basic tests to show how the execution works.

If you don’t know how to use maven, there is a quick start guide at the end of this article. You will need to read it in order to compile and run these tests.

public void testSimpleProcessExecution(){
Definition definition = new Definition("myFirstProcess");
System.out.println("########################################");
System.out.println(" Executing PROCESS:
"+definition.getName()+" ");
System.out.println("########################################");
Node firstNode = new Node("First Node");
Node secondNode = new Node("Second Node");
Node thirdNode = new Node("Third Node");
firstNode.addTransition("to second node", secondNode);
secondNode.addTransition("to third node", thirdNode);
//Add an action in the second node.
CustomAction implements Action
secondNode.addAction(new CustomAction("First"));
definition.addNode(firstNode);
definition.addNode(secondNode);
definition.addNode(thirdNode);
//We can graph it if we want.
//definition.graph();
Execution execution = new Execution (definition);
execution.start();
//The execution leave the third node
assertEquals("Third Node", execution.getCurrentNode().getName());
}

If you run this first test, it creates a process definition as in the definition tests, and then using the definition, it creates a new execution. This execution lets us interact with the process. As this is a simple implementation, we only have the start() method that starts the execution of our process, executing the logic inside each node. In this case, each node is responsible for continuing the execution to the next node. This means that there are no wait state nodes inside the example process. In case we have a wait state, our process will stop the execution in the first wait state. So, we need to interact with the process again in order to continue the execution.

Feel free to debug this test to see how this works. Analyze the code and follow the execution step by step. Try to add new actions to the nodes and analyze how all of the classes in the project behave.

When you get the idea, the framework internals will be easy to digest.

LEAVE A REPLY

Please enter your comment!
Please enter your name here