Getting Started with Process Automation
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.
More extensive documentation on these steps can be found in the Reference Guides: General Overview
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
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 applicationApplicationThe overarching model container grouping all components, domain logic, and configurations for an enterprise solution.. We start by adding the process-automation-component
expansion resourceExpansion Resource (ExpansionResource). A reusable resource bundle containing expanders and features that provide specific application capabilities, defined in the expansion settings and added to the classpath during expansion.. Additionally, you may want to add the process-automation-tracing expansion resourceExpansion Resource (ExpansionResource). A reusable resource bundle containing expanders and features that provide specific application capabilities, defined in the expansion settings and added to the classpath during expansion.. This will
add a log statement for every executed transition within your applicationApplicationThe overarching model container grouping all components, domain logic, and configurations for an enterprise solution..
- µRadiant
- Project Files
- Navigate to settings > expansionResources.
- Add a row with name
{processAutomation.groupId}:{processAutomation.artifactId}and version{processAutomation.version}. - (Optional) Add a row with name
{processAutomationTracing.groupId}:{processAutomationTracing.artifactId}and version{processAutomationTracing.version}.
<expansionSettings>
<expansionResources>
<expansionResource name="${processAutomation.groupId}:${processAutomation.artifactId}" version="${processAutomation.version}"/>
<!-- optionally -->
<expansionResource name="${processAutomationTracing.groupId}:${processAutomationTracing.artifactId}" version="${processAutomationTracing.version}"/>
</expansionResources>
</expansionSettings>
Finally, we need to add the component itself into the applicationApplicationThe overarching model container grouping all components, domain logic, and configurations for an enterprise solution. model. Add processAutomation to the applicationApplicationThe overarching model container grouping all components, domain logic, and configurations for an enterprise solution. 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 applicationsApplicationThe overarching model container grouping all components, domain logic, and configurations for an enterprise solution. 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 fieldFieldA property of a DataElement expanded to a database column and user interface input element. is added to a known DataElement to maintain the active state of a
specific DataElement instance.
- µRadiant
- $ CLI
- Model Files
- Navigate to your Component.
- In the tree-view, open the context menu of the target DataElement (right-click).
- Select "Create workflow environment". This will create a
«DataElement»Flowworkflow element.
Run the following command to transmute the model.
mvn expanders:transmute-model -Dtransmutation=CreateWorkflowEnvironment -DtargetElement={component}::{dataElement}
Running the transmutation will add a status fieldFieldA property of a DataElement expanded to a database column and user interface input element. of type WorkflowState.String with a corresponding Se finderFinderA search specification combining field-operator pairs to execute standard data queries on a DataElement. to the targeted element. Furthermore a workflow will be created.
<workflow>
<name>MyElementFlow</name>
<packageName>net.demo</packageName>
<targetField>mycomp::MyElement::status</targetField>
<states/>
<transitions/>
</workflow>
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 anchorAnchorA marker within generated source code reserved for adding custom logic during re-expansion. on the «DataElement»Bean.
- µRadiant
- Model Files
- In the tree-view, open the context menu of the Workflow (right-click).
- Select "New State". This will open the edit screen of a new state.
- Give the state a name and enable the
initialStatecheckbox to mark it as initial state. - Click 'Create'.
<workflow>
<name>MyElementFlow</name>
<states>
<state>
<name>Initial</name>
<initialState>true</initialState>
</state>
</states>
</workflow>
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. Creating a transition is similar to crating 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.
- µRadiant
- Model Files
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.
- In the tree-view, open the context menu of the Workflow (right-click).
- Select "New Transition". This will open the edit screen of a new state.
- Give the transition a name and select the
beginStatefrom the provided dropdown. - (Optional) Explicitly select an interim and/or failed state, these are generated otherwise.
- Click " New workflows::EndState::model" under
endStates - Select a
statefrom the dropdown in the newly created row. LeavetaskOutcomeempty. - Click 'Create'.
<workflow>
<name>MyElementFlow</name>
<states>
<state>
<name>Initial</name>
<initialState>true</initialState>
</state>
<state>
<name>Making</name>
</state>
<state>
<name>FailedToMake</name>
</state>
<state>
<name>Made</name>
</state>
</states>
<transitions>
<transition>
<name>Make</name>
<beginState>mycomp::MyElementFlow::Initial</beginState>
<interimState>mycomp::MyElementFlow::Making</interimState>
<failedState>mycomp::MyElementFlow::FailedToMake</failedState>
<endStates>
<endState>
<state>mycomp::MyElementFlow::Made</failedState>
</endState>
</endStates>
</transition>
</transitions>
</workflow>
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 fieldFieldA property of a DataElement expanded to a database column and user interface input element. 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.
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.
Since the TaskOutcomes are decoupled from the resulting state, it is possible to re-use (branching) Tasks in multiple transitions.
- µRadiant
- Model Files
Adding a TaskOutcome to the TaskElement (repeatable):
- In the tree-view, open the context menu of the Task (right-click).
- Select "New TaskOutcome". This will open the edit screen of a new outcome.
- Give the outcome a name and optionally a description.
- Click 'Create'
Defining endStates on a Transition with TaskOutcomes
- In the tree-view, find and select the Transition (left-click).
- Under
endStates, add as many rows as you need to fit all outcomes. - In each row, select an outcome from the
taskOutcomedropdown and an end state from thestatedropdown.
<taskElement name="ValidateMyElement">
<targetElement component="mycomp" name="MyElement"/>
<outcomes>
<taskOutcome name="Valid"/>
<taskOutcome name="Invalid"/>
</outcomes>
</taskElement>
<workflow>
<name>MyElementFlow</name>
<states>
<state>
<name>Initial</name>
<initialState>true</initialState>
</state>
<state>
<name>Validating</name>
</state>
<state>
<name>ValidationFailed</name>
</state>
<state>
<name>MyElementValid</name>
</state>
<state>
<name>MyElementInvalid</name>
</state>
</states>
<transitions>
<transition>
<name>Validate</name>
<executeTask>mycomp::ValidateMyElement</executeTask>
<beginState>mycomp::MyElementFlow::Initial</beginState>
<interimState>mycomp::MyElementFlow::Validating</interimState>
<failedState>mycomp::MyElementFlow::ValidationFailed</failedState>
<endStates>
<endState>
<state>mycomp::MyElementFlow::MyElementValid</state>
<taskOutcome>mycomp::ValidateMyElement::Valid</taskOutcome>
</endState>
<endState>
<state>mycomp::MyElementFlow::MyElementInvalid</state>
<taskOutcome>mycomp::ValidateMyElement::Invalid</taskOutcome>
</endState>
</endStates>
</transition>
</transitions>
</workflow>
Modeling behavior
So far we've only discussed what our applicationApplicationThe overarching model container grouping all components, domain logic, and configurations for an enterprise solution. 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.
- µRadiant
- Model Files
- In the tree-view, open the context menu of the Transition (right-click).
- Select "New <trigger>" where <trigger> is the desired trigger. This will open the edit screen of a new Trigger.
- Configure the trigger. Most triggers only need a name, others will be described more in the coming sections.
- Click 'Create'
<workflow>
<name>MyElementFlow</name>
<transitions>
<transition>
<name>Make</name>
<!-- other fields omitted for brevity -->
<triggers>
<trigger type="workflows::OnCreate">
<name>on:create</name>
</trigger>
<trigger type="workflows::FlowEngine">
<name>flow:engine</name>
</trigger>
</triggers>
</transition>
</transitions>
</workflow>
The only trigger that demands extra effort is the ByDataCommand trigger.
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 applicationApplicationThe overarching model container grouping all components, domain logic, and configurations for an enterprise solution..
- µRadiant
- Model Files
- In the tree-view, open the context menu of the DataElement (right-click).
- Select "New DataCommand". This will open the edit screen of a new DataCommand.
- Name the dataCommand and check 'hasTargetInstance'.
- (Optional) Add
workflow.trigger.buttonandworkflow.trigger.blockingMsoptions for additional configuration. - Click 'Create'
- The newly created dataCommand can now be selected as the
dataCommandwhen creating a ByDataCommand trigger.
<dataElement name="MyElement">
<dataCommands>
<dataCommand name="trigger">
<hasTargetInstance value="true"/>
<options>
<workflow.trigger.button/>
<workflow.trigger.blockingMs>500</workflow.trigger.blockingMs>
</options>
</dataCommand>
</dataCommands>
</dataElement>
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.
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.
- µRadiant
- Model Files
- In the tree-view, find and select the Transition (left-click).
- Select the
recoveryStatefrom the dropdown or leave it empty to use the default.
<workflow>
<name>MyElementFlow</name>
<transitions>
<transition>
<name>Make</name>
<!-- other fields omitted for brevity -->
<recoveryState>mycomp::MyElementFlow::MakeFailed</recoveryState>
</transition>
</transitions>
</workflow>
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.
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.