Skip to main content

Getting started

The process automation component is designed to gradually increase complexity when needed. This guide will take you through the most common functionality of the component and set you up to tackle more advanced use-cases later.

Prerequisites

This guide assumes some (basic) prior knowledge. It is advised to read up on these topics if they are not immediately clear yet.

  • Component model: We will extend the Component model with Workflows.
  • Finite state machines: Processing is modeled using state machines. We will refer to state machine terminology such as state and transition.

Installing the component

When using workflow component

If you are already using the workflow component, make sure to migrate it first. Go to migration guide.

The first step is to add the component to your application. We start by adding the process-automation-component expansion resource. Additionally, you may want to add the process-automation-tracing expansion resource. This will add a log statement for every executed transition within your application.

  1. Navigate to settings > expansionResources.
  2. Add a row with name net.democritus.workflow:process-automation-component and version 1.5.4.
  3. (Optional) Add a row with name net.democritus.workflow:process-automation-tracing and version 1.5.4.

Finally, we need to add the component itself into the application model. Add processAutomation to the application components and don't forget to add processAutomation as a component dependency to the components that will use it as well.

Creating a workflow

We will mostly be working with the Workflow model, which is an extension of Component. This means it lives next to (and not inside) DataElement and TaskElement. Each workflow defines a single state machine. Most applications are too complex to put into a single state-machine, which is why multiple state machines can run in parallel.

Selecting a target

The state within our state machine needs to be stored somewhere. Since we already model data using DataElements, we will use these as a target for workflows. A String field is added to a known DataElement to maintain the active state of a specific DataElement instance.

  1. Navigate to your Component.
  2. In the tree-view, open the context menu of the target DataElement (right-click).
  3. Select "Create workflow environment". This will create a «DataElement»Flow workflow element.

Marking the initial state

A workflow defines a set of states, which lists all possible states in our state-machine. Each state has a name property to make it easier when reasoning about a workflow. Additionally one state can be marked as the initial state. As a result of defining the initial state, every instance from the targeted DataElement will start with a status value equal to the name of the initial state. This value is set in the pre-create anchor on the «DataElement»Bean.

  1. In the tree-view, open the context menu of the Workflow (right-click).
  2. Select "New State". This will open the edit screen of a new state.
  3. Give the state a name and enable the initialState checkbox to mark it as initial state.
  4. Click 'Create'.
caution

A state machine should always have exactly one initial state. Read the documentation on initial states to see how having multiple initial states can be prevented.

Adding transitions

Now that we have the basic elements in place, it is time to add functionality. We define transitions to model functionality within a workflow. A workflow transition however is slightly different from a classical state-machine transition. A workflow transition in fact defines three or more transitions. The main components are a begin state, interim state, failed state and end state. The state machine of a single workflow transition is depicted below.


info

You may wonder why we define an interim state. The primary reason is to facilitate a separation of state, it becomes immediately obvious when a dataElement is being processed by a workflow, and when it is finished. A second more technical reason is related to locking. Enterprise applications have a great concurrency model, but we do not want to concurrently modify the same object. By eagerly modifying the state to an interim state, we can avoid such modifications.

A simple transition

Now lets actually create a transition. This is similar to creating a state, but we will have to enter the related states. You may notice the end state to be defined in a list of endStates, we will discuss this further in Branching tasks.

Before creating a transition, it is advised to have a begin and end state already present. For interim and failed state the µRadiant provides a generator.

  1. In the tree-view, open the context menu of the Workflow (right-click).
  2. Select "New Transition". This will open the edit screen of a new state.
  3. Give the transition a name and select the beginState from the provided dropdown.
  4. (Optional) Explicitly select an interim and/or failed state, these are generated otherwise.
  5. Click " New workflows::EndState::model" under endStates
  6. Select a state from the dropdown in the newly created row. Leave taskOutcome empty.
  7. Click 'Create'.

Executing tasks

Now that we can define the stateful lifecycle of a dataElement, it is time to perform some actual work! We already model each unit of work as a TaskElement which makes it a good candidate for integration with workflows. To execute a task within a transition, simply configure the task as executeTask field on the transition.

One thing to consider is the that the targetField on a workflow must be from the same DataElement as the TaskElement targetElement. The task will contain any business logic needed for the transition. Below is the updated version of our previous example, where the only two results the task can have are a TaskResult.success and a TaskResult.error.

Branching tasks

With tasks as discussed so far it is already possible to model many use-cases for a workflow. By introducing branching the model can become even more expressive. A TaskElement allows you to define a set of TaskOutcomes. Each outcome can then be interpreted by a transition in order to determine the correct end state. Consider the example of Schrödingers cat and a task to open the box. The following is a more concrete example of the diagram shown earlier.


Since the TaskOutcomes are decoupled from the resulting state, it is possible to re-use (branching) Tasks in multiple transitions.

Adding a TaskOutcome to the TaskElement (repeatable):

  1. In the tree-view, open the context menu of the Task (right-click).
  2. Select "New TaskOutcome". This will open the edit screen of a new outcome.
  3. Give the outcome a name and optionally a description.
  4. Click 'Create'

Defining endStates on a Transition with TaskOutcomes

  1. In the tree-view, find and select the Transition (left-click).
  2. Under endStates, add as many rows as you need to fit all outcomes.
  3. In each row, select an outcome from the taskOutcome dropdown and an end state from the state dropdown.

Modeling behavior

So far we've only discussed what our application should be doing, but we are missing when this should be done. The workflow model has the concept of Triggers which model the behavior of transitions. Process automation ships with several useful triggers out of the box: OnCreate, OnModify, OnTransition, ByDataCommand, Schedule and FlowEngine.

Creating triggers is very similar regardless of its type.

  1. In the tree-view, open the context menu of the Transition (right-click).
  2. Select "New <trigger>" where <trigger> is the desired trigger. This will open the edit screen of a new Trigger.
  3. Configure the trigger. Most triggers only need a name, others will be described more in the coming sections.
  4. Click 'Create'

OnCreate

It is not uncommon for dataElements to require some pre-processing after they are created. The OnCreate trigger is perfect for such scenario. The result of adding this trigger is that a transition is immediately added to the processing queue when a dataElement is created, causing a near instant transition as a result. This should only be used on transitions with the initial state as their begin state.

OnModify

Very similar to the OnCreate trigger, but is activated when a DataElement is modified. This is useful when additional processing or validation is required after a user has changed the element in some way.

OnTransition

Another event based trigger. OnTransition will activate after each transition within the workflow. This makes it suitable for chaining multiple transitions together. It is recommended to keep Task implementations as small as possible, by using this Trigger the induced overhead of multiple sequential Transitions is minimal.

ByDataCommand

We often see certain transitions originate in user interaction. To facilitate this, the ByDataCommand trigger integrates execution of a transition within a command.

In order for this to work, we first need to define a dataCommand. This command has two important options to customize behavior. By default, the command will only schedule the transition and return immediately. To make the command wait while transitioning, the workflow.trigger.blockingMs option can be either set to a positive integer (the timeout) or to the value until_complete (no timeout). The options workflow.trigger.button can be used to conveniently add a button to the knockout UI, if present in your application.

  1. In the tree-view, open the context menu of the DataElement (right-click).
  2. Select "New DataCommand". This will open the edit screen of a new DataCommand.
  3. Name the dataCommand and check 'hasTargetInstance'.
  4. (Optional) Add workflow.trigger.button and workflow.trigger.blockingMs options for additional configuration.
  5. Click 'Create'
  6. The newly created dataCommand can now be selected as the dataCommand when creating a ByDataCommand trigger.
tip

The ByDataCommand trigger is an abstraction well suited for integration-testing workflow transitions. Its independence on time or other (uncontrollable) events makes it great for swift testing without working around stateful execution.

Schedule

Some actions may be bound to time. For example sending out messages at a specific time or requesting data from an external service at regular intervals. The Schedule trigger is added to achieve this using a cron-like schedule. The default values in the schedule result in "Every day at 00:00 local time".

fielddefaultexample
second0*/30 "every 30 seconds"
minute0*/5 "every 5 minutes"
hour01,13 "twice a day at 1 'o clock"
dayOfWeek*Tue,Fri "every tuesday and friday"
dayOfMonth*1 "first of every month"
month*1,2 "only in January and February
year*2025 "only in 2025"
timezoneJVM timezoneEurope/Amsterdam

FlowEngine

Primarily added to ease the migration from the 'workflow' component to process automation. Results in a single interval timer per workflow which fetches all elements which are in the beginState of any transition with the FlowEngine trigger.

The main difference is that this will only perform transitions of the current state, and not traverse the whole transition graph. If you need this behavior, configure the follow-up transitions with the OnTransition trigger.

There are various settings to tweak the behavior of FlowEngines, which are documented on the triggers page.

info

FlowEngines have a limited batch size unless configured otherwise. In case the begin- and endState are equal, you may need to define a sorting such that each element is picked up eventually. Use the flowEngine.sortField option to configure this sorting.

Option
flowEngine.sortField Field

Let flowEngines on this element sort their batches by the field this option applies to. Sorting direction can be specified through the value and defaults to asc. Fields annotated with the audit.modify.timestamp option will automatically be added to the sortFields.

<options>
<flowEngine.sortField/>
</options>
<options>
<flowEngine.sortField>(asc|desc)</flowEngine.sortField>
</options>

Transition recovery

It is possible a transition cannot complete successfully. As a result a DataElement may be stuck in an interim state. To resolve this issue process automation attempts a recovery procedure at startup. The default behavior is to move to the failed state.

Recovery state

Alternatively, the recovery state can explicitly be configured on the Transition. Simply select a recovery state from the dropdown to configure it as the state to be used when recovering the transition.

  1. In the tree-view, find and select the Transition (left-click).
  2. Select the recoveryState from the dropdown or leave it empty to use the default.

Transactions

It is possible to use the begin state as the recovery state with one restriction. The executed task must be transactional. In order for recovery to the begin state to be valid, the task must not have performed any side effects. Having a transaction around a task at least ensures there were no database modifications as a result of a failed task.

Note

You still need to make sure no other side effects were performed within the task. For example when making a request to an external api, a compensating request may be needed in order to cancel the initial request.