The engine API is the most common way of interacting with Flowable. The main starting point is the ProcessEngine, which can be created in several ways as described in the configuration section. From the ProcessEngine, you can obtain the various services that contain the workflow/BPM methods. ProcessEngine and the services objects are thread safe, so you can keep a reference to one of those for a whole server.
ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
RuntimeService runtimeService = processEngine.getRuntimeService();
RepositoryService repositoryService = processEngine.getRepositoryService();
TaskService taskService = processEngine.getTaskService();
ManagementService managementService = processEngine.getManagementService();
IdentityService identityService = processEngine.getIdentityService();
HistoryService historyService = processEngine.getHistoryService();
FormService formService = processEngine.getFormService();
DynamicBpmnService dynamicBpmnService = processEngine.getDynamicBpmnService();
ProcessEngines.getDefaultProcessEngine() will initialize and build a process engine the first time it is called and afterwards always return the same process engine. Proper creation and closing of all process engines can be done with ProcessEngines.init() and ProcessEngines.destroy().
The ProcessEngines class will scan for all flowable.cfg.xml and flowable-context.xml files. For all flowable.cfg.xml files, the process engine will be built in the typical Flowable way: ProcessEngineConfiguration.createProcessEngineConfigurationFromInputStream(inputStream).buildProcessEngine(). For all flowable-context.xml files, the process engine will be built in the Spring way: first the Spring application context is created and then the process engine is obtained from that application context.
All services are stateless. This means that you can easily run Flowable on multiple nodes in a cluster, each going to the same database, without having to worry about which machine actually executed previous calls. Any call to any service is idempotent regardless of where it is executed.
The RepositoryService is probably the first service needed when working with the Flowable engine. This service offers operations for managing and manipulating deployments and process definitions. Without going into much detail here, a process definition is a Java counterpart of the BPMN 2.0 process. It is a representation of the structure and behavior of each of the steps of a process. A deployment is the unit of packaging within the Flowable engine. A deployment can contain multiple BPMN 2.0 XML files and any other resource. The choice of what is included in one deployment is up to the developer. It can range from a single process BPMN 2.0 XML file to a whole package of processes and relevant resources (for example, the deployment 'hr-processes' could contain everything related to HR processes). The RepositoryService can deploy such packages. Deploying a deployment means it is uploaded to the engine, where all processes are inspected and parsed before being stored in the database. From that point on, the deployment is known to the system and any process included in the deployment can now be started.
Furthermore, this service allows you to:
-
Query on deployments and process definitions known to the engine.
-
Suspend and activate deployments as a whole or specific process definitions. Suspending means no further operations can be performed on them, while activation is the opposite and enables operations again.
-
Retrieve various resources, such as files contained within the deployment or process diagrams that were auto-generated by the engine.
-
Retrieve a POJO version of the process definition, which can be used to introspect the process using Java rather than XML.
While the RepositoryService is mostly about static information (data that doesn’t change, or at least not a lot), the RuntimeService is quite the opposite. It deals with starting new process instances of process definitions. As said above, a process definition defines the structure and behavior of the different steps in a process. A process instance is one execution of such a process definition. For each process definition there typically are many instances running at the same time. The RuntimeService also is the service which is used to retrieve and store process variables. This is data that is specific to the given process instance and can be used by various constructs in the process (for example, an exclusive gateway often uses process variables to determine which path is chosen to continue the process). The Runtimeservice also allows you to query on process instances and executions. Executions are a representation of the 'token' concept of BPMN 2.0. Basically, an execution is a pointer to where the process instance currently is. Lastly, the RuntimeService is used whenever a process instance is waiting for an external trigger and the process needs to be continued. A process instance can have various wait states and this service contains various operations to 'signal' to the instance that the external trigger is received and the process instance can be continued.
Tasks that need to be performed by human users of the system are core to a BPM engine such as Flowable. Everything around tasks is grouped in the TaskService, such as:
-
Querying tasks assigned to users or groups
-
Creating new standalone tasks. These are tasks that are not related to a process instances.
-
Manipulating to which user a task is assigned or which users are in some way involved with the task.
-
Claiming and completing a task. Claiming means that someone decided to be the assignee for the task, meaning that this user will complete the task. Completing means 'doing the work of the tasks'. Typically this is filling in a form of sorts.
The IdentityService is pretty simple. It supports the management (creation, update, deletion, querying, …) of groups and users. It is important to understand that Flowable actually doesn’t do any checking on users at runtime. For example, a task could be assigned to any user, but the engine doesn’t verify whether that user is known to the system. This is because the Flowable engine can also be used in conjunction with services such as LDAP, Active Directory, and so on.
The FormService is an optional service. Meaning that Flowable can be used quite happily without it, without sacrificing any functionality. This service introduces the concept of a start form and a task form. A start form is a form that is shown to the user before the process instance is started, while a task form is the form that is displayed when a user wants to complete a form. Flowable allows the specification of these forms in the BPMN 2.0 process definition. This service exposes this data in an easy way to work with. But again, this is optional, as forms don’t need to be embedded in the process definition.
The HistoryService exposes all historical data gathered by the Flowable engine. When executing processes, a lot of data can be kept by the engine (this is configurable), such as process instance start times, who did which tasks, how long it took to complete the tasks, which path was followed in each process instance, and so on. This service exposes mainly query capabilities to access this data.
The ManagementService is typically not needed when coding custom application using Flowable. It allows the retrieval of information about the database tables and table metadata. Furthermore, it exposes query capabilities and management operations for jobs. Jobs are used in Flowable for various things, such as timers, asynchronous continuations, delayed suspension/activation, and so on. Later on, these topics will be discussed in more detail.
The DynamicBpmnService can be used to change part of the process definition without needing to redeploy it. You can, for example, change the assignee definition for a user task in a process definition, or change the class name of a service task.
For more detailed information on the service operations and the engine API, see the javadocs.
The base exception in Flowable is the org.flowable.engine.FlowableException, an unchecked exception. This exception can be thrown at all times by the API, but 'expected' exceptions that happen in specific methods are documented in the the javadocs. For example, an extract from TaskService:
/**
* Called when the task is successfully executed.
* @param taskId the id of the task to complete, cannot be null.
* @throws FlowableObjectNotFoundException when no task exists with the given id.
*/
void complete(String taskId);
In the example above, when an id is passed for which no task exists, an exception will be thrown. Also, since the javadoc explicitly states that taskId cannot be null, an FlowableIllegalArgumentException will be thrown when null is passed.
Even though we want to avoid a big exception hierarchy, the following subclasses are thrown in specific cases. All other errors that occur during process-execution or API-invocation that don’t fit into the possible exceptions below, are thrown as regular FlowableExceptionss.
-
FlowableWrongDbException: Thrown when the Flowable engine discovers a mismatch between the database schema version and the engine version.
-
FlowableOptimisticLockingException: Thrown when an optimistic locking occurs in the data store caused by concurrent access of the same data entry.
-
FlowableClassLoadingException: Thrown when a class requested to load was not found or when an error occurred while loading it (e.g. JavaDelegates, TaskListeners, …).
-
FlowableObjectNotFoundException: Thrown when an object that is requested or actioned does not exist.
-
FlowableIllegalArgumentException: An exception indicating that an illegal argument has been supplied in a Flowable API-call, an illegal value was configured in the engine’s configuration or an illegal value has been supplied or an illegal value is used in a process-definition.
-
FlowableTaskAlreadyClaimedException: Thrown when a task is already claimed, when the taskService.claim(...) is called.
There are two ways of querying data from the engine: the query API and native queries. The Query API allows you to program completely typesafe queries with a fluent API. You can add various conditions to your queries (all of which are applied together as a logical AND) and precisely one ordering. The following code shows an example:
List<Task> tasks = taskService.createTaskQuery()
.taskAssignee("kermit")
.processVariableValueEquals("orderId", "0815")
.orderByDueDate().asc()
.list();
Sometimes you need more powerful queries, for example, queries using an OR operator or restrictions you cannot express using the Query API. For these cases, we have native queries, which allow you to write your own SQL queries. The return type is defined by the Query object you use and the data is mapped into the correct objects (Task, ProcessInstance, Execution, …). Since the query will be fired at the database you have to use table and column names as they are defined in the database; this requires some knowledge about the internal data structure and it is recommended to use native queries with care. The table names can be retrieved through the API to keep the dependency as small as possible.
List<Task> tasks = taskService.createNativeTaskQuery()
.sql("SELECT count(*) FROM " + managementService.getTableName(Task.class) +
" T WHERE T.NAME_ = #{taskName}")
.parameter("taskName", "gonzoTask")
.list();
long count = taskService.createNativeTaskQuery()
.sql("SELECT count(*) FROM " + managementService.getTableName(Task.class) + " T1, " +
managementService.getTableName(VariableInstanceEntity.class) + " V1 WHERE V1.TASK_ID_ = T1.ID_")
.count();
Every process instance needs and uses data to execute the steps it’s made up of. In Flowable, this data is called variables, which are stored in the database. Variables can be used in expressions (for example, to select the correct outgoing sequence flow in an exclusive gateway), in Java service tasks when calling external services (for example to provide the input or store the result of the service call), and so on.
A process instance can have variables (called process variables), but also executions (which are specific pointers to where the process is active) and user tasks can have variables. A process instance can have any number of variables. Each variable is stored in a row in the ACT_RU_VARIABLE database table.
All of the startProcessInstanceXXX methods have an optional parameters to provide the variables when the process instance is created and started. For example, from the RuntimeService:
ProcessInstance startProcessInstanceByKey(String processDefinitionKey, Map<String, Object> variables);
Variables can be added during process execution. For example, (RuntimeService):
void setVariable(String executionId, String variableName, Object value);
void setVariableLocal(String executionId, String variableName, Object value);
void setVariables(String executionId, Map<String, ? extends Object> variables);
void setVariablesLocal(String executionId, Map<String, ? extends Object> variables);
Note that variables can be set local for a given execution (remember, a process instance consists of a tree of executions). The variable will only be visible on that execution and not higher in the tree of executions. This can be useful if data shouldn’t be propagated to the process instance level, or the variable has a new value for a certain path in the process instance (for example, when using parallel paths).
Variables can also be retrieved, as shown below. Note that similar methods exist on the TaskService. This means that tasks, like executions, can have local variables that are 'alive' just for the duration of the task.
Map<String, Object> getVariables(String executionId);
Map<String, Object> getVariablesLocal(String executionId);
Map<String, Object> getVariables(String executionId, Collection<String> variableNames);
Map<String, Object> getVariablesLocal(String executionId, Collection<String> variableNames);
Object getVariable(String executionId, String variableName);
<T> T getVariable(String executionId, String variableName, Class<T> variableClass);
Variables are often used in Java delegates, expressions, execution- or tasklisteners, scripts, and so on. In those constructs, the current execution or task object is available and it can be used for variable setting and/or retrieval. The simplest methods are these:
execution.getVariables();
execution.getVariables(Collection<String> variableNames);
execution.getVariable(String variableName);
execution.setVariables(Map<String, object> variables);
execution.setVariable(String variableName, Object value);
Note that a variant with local is also available for all of the above.
For historical (and backwards-compatibility reasons), when doing any of the calls above, behind the scenes all variables will be fetched from the database. This means that if you have 10 variables, but only get one through getVariable("myVariable"), behind the scenes the other 9 will be fetched and cached. This is not necessarily bad, as subsequent calls will not hit the database again. For example, when your process definition has three sequential service tasks (and thus one database transaction), using one call to fetch all variables in the first service task might be better then fetching the variables needed in each service task separately. Note that this applies both for getting and setting variables.
Of course, when using a lot of variables or simply when you want tight control on the database query and traffic, this is not appropriate. Additional methods have been introduced to give a tighter control on this, by adding in new methods that have an optional parameter that tells the engine whether or not to fetch and cache all variables:
Map<String, Object> getVariables(Collection<String> variableNames, boolean fetchAllVariables);
Object getVariable(String variableName, boolean fetchAllVariables);
void setVariable(String variableName, Object value, boolean fetchAllVariables);
When using true for the parameter fetchAllVariables, the behavior will be exactly as described above: when getting or setting a variable, all other variables will be fetched and cached.
However, when using false as value, a specific query will be used and no other variables will be fetched or cached. Only the value of the variable in question here will be cached for subsequent use.
Transient variables are variables that behave like regular variables, but are not persisted. Typically, transient variables are used for advanced use cases. When in doubt, use a regular process variable.
The following applies for transient variables:
-
There is no history stored at all for transient variables.
-
Like regular variables, transient variables are put on the highest parent when set. This means that when setting a variable on an execution, the transient variable is actually stored on the process instance execution. Like regular variables, a local variant of the method exists if the variable is set on the specific execution or task.
-
A transient variable can only be accessed before the next 'wait state' in the process definition. After that, they are gone. Here, the wait state means the point in the process instance where it is persisted to the data store. Note that an async activity is also a 'wait state' in this definition!
-
Transient variables can only be set by the setTransientVariable(name, value), but transient variables are also returned when calling getVariable(name) (a getTransientVariable(name) also exists, that only checks the transient variables). The reason for this is to make the writing of expressions easy and existing logic using variables works for both types.
-
A transient variable shadows a persistent variable with the same name. This means that when both a persistent and transient variable is set on a process instance and getVariable("someVariable") is called, the transient variable value will be returned.
You can set and get transient variables in most places where regular variables are exposed:
-
On DelegateExecution in JavaDelegate implementations
-
On DelegateExecution in ExecutionListener implementations and on DelegateTask on TaskListener implementations
-
In script task via the execution object
-
When starting a process instance through the runtime service
-
When completing a task
-
When calling the runtimeService.trigger method
The methods follow the naming convention of the regular process variables:
void setTransientVariable(String variableName, Object variableValue);
void setTransientVariableLocal(String variableName, Object variableValue);
void setTransientVariables(Map<String, Object> transientVariables);
void setTransientVariablesLocal(Map<String, Object> transientVariables);
Object getTransientVariable(String variableName);
Object getTransientVariableLocal(String variableName);
Map<String, Object> getTransientVariables();
Map<String, Object> getTransientVariablesLocal();
void removeTransientVariable(String variableName);
void removeTransientVariableLocal(String variableName);
The following BPMN diagram shows a typical example:
Let’s assume the 'Fetch Data' service task calls some remote service (for example, using REST). Let’s also assume some configuration parameters are needed and need to be provided when starting the process instance. Also, these configuration parameters are not important for historical audit purposes, so we pass them as transient variables:
ProcessInstance processInstance = runtimeService.createProcessInstanceBuilder()
.processDefinitionKey("someKey")
.transientVariable("configParam01", "A")
.transientVariable("configParam02", "B")
.transientVariable("configParam03", "C")
.start();
Note that the transient variables will be available until the user task is reached and persisted to the database. For example, in the 'Additional Work' user task they are no longer available. Also note that if 'Fetch Data' had been asynchronous, they wouldn’t be available after that step either.
The 'Fetch Data' (simplified) could be something like:
public static class FetchDataServiceTask implements JavaDelegate {
public void execute(DelegateExecution execution) {
String configParam01 = (String) execution.getVariable(configParam01);
// ...
RestReponse restResponse = executeRestCall();
execution.setTransientVariable("response", restResponse.getBody());
execution.setTransientVariable("status", restResponse.getStatus());
}
}
The 'Process Data' would get the response transient variable, parse it and store the relevant data in real process variables as we need them later.
The condition on the sequence flow leaving the exclusive gateway are oblivious to whether persistent or transient variables are used (in this case, the status transient variable):
<conditionExpression xsi:type="tFormalExpression">${status == 200}</conditionExpression>
Flowable uses UEL for expression-resolving. UEL stands for Unified Expression Language and is part of the EE6 specification (see the EE6 specification for detailed information).
Expressions can be used in, for example, Java Service tasks, Execution Listeners, Task Listeners and Conditional sequence flows. Although there are two types of expressions, value-expression and method-expression, Flowable abstracts this so they can both be used where an expression is expected.
-
Value expression: resolves to a value. By default, all process variables are available to use. Also, all spring-beans (if using Spring) are available to use in expressions. Some examples:
${myVar} ${myBean.myProperty}
-
Method expression: invokes a method with or without parameters. When invoking a method without parameters, be sure to add empty parentheses after the method-name (as this distinguishes the expression from a value expression). The passed parameters can be literal values or expressions that are resolved themselves. Examples:
${printer.print()} ${myBean.addNewOrder('orderName')} ${myBean.doSomething(myVar, execution)}
Note that these expressions support resolving primitives (including comparing them), beans, lists, arrays and maps.
On top of all process variables, there are a few default objects available that can be used in expressions:
-
execution: The DelegateExecution holds additional information about the ongoing execution.
-
task: The DelegateTask holds additional information about the current Task. Note: Only works in expressions evaluated from task listeners.
-
authenticatedUserId: The id of the user that is currently authenticated. If no user is authenticated, the variable is not available.
For more concrete usage and examples, check out Expressions in Spring, Java Service tasks, Execution Listeners, Task Listeners or Conditional sequence flows.
Business processes are an integral part of software projects and they should be tested in the same way normal application logic is tested: with unit tests. Since Flowable is an embeddable Java engine, writing unit tests for business processes is as simple as writing regular unit tests.
Flowable supports both JUnit versions 3 and 4 styles of unit testing. In the JUnit 3 style, the org.flowable.engine.test.FlowableTestCase must be extended. This will make the ProcessEngine and the services available through protected member fields. In the setup() of the test, the processEngine will be initialized by default with the flowable.cfg.xml resource on the classpath. To specify a different configuration file, override the getConfigurationResource() method. Process engines are cached statically over multiple unit tests when the configuration resource is the same.
By extending FlowableTestCase, you can annotate test methods with org.flowable.engine.test.Deployment. Before the test is run, a resource file of the form testClassName.testMethod.bpmn20.xml in the same package as the test class, will be deployed. At the end of the test, the deployment will be deleted, including all related process instances, tasks, and so on. The Deployment annotation also supports setting the resource location explicitly. See the class itself for more information.
Taking all that in account, a JUnit 3 style test looks as follows.
public class MyBusinessProcessTest extends FlowableTestCase {
@Deployment
public void testSimpleProcess() {
runtimeService.startProcessInstanceByKey("simpleProcess");
Task task = taskService.createTaskQuery().singleResult();
assertEquals("My Task", task.getName());
taskService.complete(task.getId());
assertEquals(0, runtimeService.createProcessInstanceQuery().count());
}
}
To get the same functionality when using the JUnit 4 style of writing unit tests, the org.flowable.engine.test.FlowableRule Rule must be used. Through this rule, the process engine and services are available through getters. As with the FlowableTestCase (see above), including this Rule will enable the use of the org.flowable.engine.test.Deployment annotation (see above for an explanation of its use and configuration) and it will look for the default configuration file on the classpath. Process engines are statically cached over multiple unit tests when using the same configuration resource.
The following code snippet shows an example of using the JUnit 4 style of testing and the usage of the FlowableRule.
public class MyBusinessProcessTest {
@Rule
public FlowableRule FlowableRule = new FlowableRule();
@Test
@Deployment
public void ruleUsageExample() {
RuntimeService runtimeService = FlowableRule.getRuntimeService();
runtimeService.startProcessInstanceByKey("ruleUsage");
TaskService taskService = FlowableRule.getTaskService();
Task task = taskService.createTaskQuery().singleResult();
assertEquals("My Task", task.getName());
taskService.complete(task.getId());
assertEquals(0, runtimeService.createProcessInstanceQuery().count());
}
}
When using the in-memory H2 database for unit tests, the following instructions allow you to easily inspect the data in the Flowable database during a debugging session. The screenshots here are taken in Eclipse, but the mechanism should be similar for other IDEs.
Suppose we have put a breakpoint somewhere in our unit test (in Eclipse this is done by double-clicking in the left border next to the code):
If we now run the unit test in debug mode (right-click in test class, select 'Run as' and then 'JUnit test'), the test execution halts at our breakpoint, where we can now inspect the variables of our test as shown in the right upper panel.
To inspect the Flowable data, open up the 'Display' window (if this window isn’t there, open Window→Show View→Other and select Display.) and type (code completion is available) org.h2.tools.Server.createWebServer("-web").start()
Select the line you’ve just typed and right-click on it. Now select 'Display' (or execute the shortcut instead of right-clicking)
Now open up a browser and go to http://localhost:8082, and fill in the JDBC URL to the in-memory database (by default this is jdbc:h2:mem:flowable), and hit the connect button.
You can now see the Flowable data and use it to understand how and why your unit test is executing your process in a certain way.
The ProcessEngine is a thread-safe class and can easily be shared among multiple threads. In a web application, this means it is possible to create the process engine once when the container boots and shut down the engine when the container goes down.
The following code snippet shows how you can write a simple ServletContextListener to initialize and destroy process engines in a plain Servlet environment:
public class ProcessEnginesServletContextListener implements ServletContextListener {
public void contextInitialized(ServletContextEvent servletContextEvent) {
ProcessEngines.init();
}
public void contextDestroyed(ServletContextEvent servletContextEvent) {
ProcessEngines.destroy();
}
}
The contextInitialized method will delegate to ProcessEngines.init(). That will look for flowable.cfg.xml resource files on the classpath, and create a ProcessEngine for the given configurations (for example, multiple JARs with a configuration file). If you have multiple such resource files on the classpath, make sure they all have different names. When the process engine is needed, it can be fetched using:
ProcessEngines.getDefaultProcessEngine()
or
ProcessEngines.getProcessEngine("myName");
Of course, it is also possible to use any of the variants of creating a process engine, as described in the configuration section.
The contextDestroyed method of the context-listener delegates to ProcessEngines.destroy(). That will properly close all initialized process engines.