workflow control patterns

The Workflow Patterns are a catalog of various building blocks for workflow execution.

Described here are ways to implement each of them (if necessary) with Ruote (OpenWFEru). Some of the patterns are not directly implementable with Ruote, workarounds are proposed.

For each pattern, an example is given in XML and in Ruby. There is also a link to the academic explanation of the pattern.


Basic Control Flow Patterns

Sequence

1   <sequence>
2     <participant ref="alpha" />
3     <participant ref="bravo" />
4   </sequence>
1   sequence do
2     alpha
3     bravo
4   end

(Ruote finding no expression and no subprocess definition for ‘alpha’ and ‘bravo’, it automatically maps them to their participants).

What else is there to say ?

original pattern explanation

Parallel Split

Splitting the process instance into two parallel path of execution.

1   <concurrence>
2     <participant ref="alpha" />
3     <participant ref="bravo" />
4   </concurrence>
1   concurrence do
2     alpha
3     bravo
4   end

concurrence expression
original pattern explanation

Synchronization

Synchronization is supported implicitely by the concurrence expression.

But the ‘concurrence’ expression can handle more patterns / scenario : the concurrence expression waits for all its branches to reply before resuming (before replying to its parent / containing expression.

concurrence expression
original pattern explanation

Exclusive Choice

Exclusive ‘routing’ within the process : the flow will go one way or the other, but not both.

Two expressions in Ruote are used to implement this pattern : ‘if’ and ‘case’.

1   <if>
2     <equals field-value="x" other-value="y" />
3     <!-- then -->
4     <participant ref="theodor" />
5     <!-- else -->
6     <participant ref="emma" />
7   </if>
1   _if do
2     equals :field_value => :x, :other-value => "y"
3     # then
4     participant :theodor
5     # else
6     participant :emma
7   end

(not to collide with Ruby’s ‘if’, Ruote’s ‘if’ is escaped with a “_” prefix in the Ruby process definition, same thing for ‘case’)

 1   <case>
 2 
 3     <equals field-value="x" other-value="y" />
 4     <participant ref="alpha" />
 5 
 6     <if test="${field:price} > 12.0" />
 7     <participant ref="bravo" />
 8 
 9     <!-- optional else -->
10     <participant ref="emma" />
11 
12   </case>

(note here the usage of ‘if’ without children as a simple condition expression)

 1   _case do
 2 
 3     equals :field_value => "x", :other_value => "y"
 4     alpha
 5 
 6     _if :test => "${field:price} > 12.0"
 7     bravo
 8 
 9     # optional else :
10     emma
11 
12   end

(In this Ruby process definition, the participants, have simple one word names are directly referenced using those, no ‘participant ref=“alpha”’ construct)

if expression
case expression
original pattern explanation

Simple Merge

The ‘simple merge’ is implicitely supported by the ‘if’ and ‘case’ expressions.

if expression
case expression
original pattern explanation


Advanced Branching and Synchronization Patterns

Multi-Choice

The straightforward way for implementing this pattern with Ruote is to use a set of ‘if’ nested within a ‘concurrence’ :

 1   <concurrence>
 2     <if test="${field:price} > 12.0" >
 3       <participant ref="alpha" />
 4     </if>
 5     <if>
 6       <equals field="price" value="0">
 7       <participant ref="bravo" />
 8     </if>
 9     <if test="${f:price} > 100.0" >
10       <participant ref="charly" />
11     </if>
12   </concurrence>

Not that if the price is superior to 100, both the participant alpha and the participant charly will ‘receive the flow’.

 1  concurrence do
 2    _if :test => "${field:price} > 12.0" do
 3      participant :alpha
 4    end
 5    _if do
 6      _equals :field => "price", :value => "0"
 7      participant :ref => :bravo
 8    end
 9    _if :test => "${f:price} > 100.0" do
10      participant :ref => "charly"
11    end
12  end

(note here that the participant expression is very flexible, pick the way to use it that is the more readable for you)

The short version :

1   concurrence do
2     alpha :if => "${f:price} > 12.0"
3     bravo :if => "${f:price} == 0"
4     charly :if => "${f:price} > 100.0"
5   end

(both the ‘participant’ and the ‘subprocess’ support this ‘if’ (and ‘unless’) attribute.

if expression
concurrence expression
original pattern explanation

Structured Synchronizing Merge

The Ruote implementation of pattern 6 ‘Multi-Choice’ implicitely follows pattern 7 as well. The ‘concurrence’ expression, by default, waits for all its children to reply before resuming the flow (replying to its own parent expression).

concurrence expression
original pattern explanation

Multi-Merge

This pattern is very interesting, it shows the difference between a graph-based workflow engine and Ruote.

The “merge” after the “multi-choice” in that pattern is not synchronized. In the previous pattern, the workitems of the concurrent path got reunified into one workitem for the steps after the concurrence. Here, each concurrent workitem follows the ‘track’ indepedently.

Here’s how you can implement that with Ruote :

 1   <process-definition name="pat8multimerge" revision="2">
 2 
 3     <concurrence>
 4       <if test="${field:price} > 12.0" >
 5         <sequence>
 6           <participant ref="alpha" />
 7           <after/>
 8         </sequence>
 9       </if>
10       <if>
11         <equals field="price" value="0">
12         <sequence>
13           <participant ref="bravo" />
14           <after/>
15         </sequence>
16       </if>
17       <if test="${f:price} > 100.0" >
18         <sequence>
19           <participant ref="charly" />
20           <after/>
21         </sequence>
22       </if>
23     </concurrence>
24 
25     <process-definition name="after">
26       <sequence>
27         <participant ref="delta" />
28         <participant ref="echo" />
29       </sequence>
30     </process-definition>
31   </process-definition>

Each concurrent workitem will make its way to its own instance of the ‘after’ subprocess.

 1   require 'openwfe/def'
 2 
 3   class Pat8MultiMerge2 < OpenWFE::ProcessDefinition
 4 
 5     concurrence do
 6       _if :test => "${field:price} > 12.0" do
 7         sequence do
 8           alpha
 9           after
10         end
11       end
12       _if do
13         equals :field => "price", :value => "0"
14         sequence do
15           bravo
16           after
17         end
18       end
19       _if :test => "${f:price} > 100.0" do
20         sequence do
21           charly
22           after
23         end
24       end
25     end
26 
27     process_definition :name => "after" do
28       sequence do
29         delta
30         echo
31       end
32     end
33   end

original pattern explanation

Structured Discriminator

As soon as a concurrent branch terminates, the concurrence expression resumes the flow.

1   <sequence>
2     <participant ref="alpha" />
3     <concurrence count="1" remaining="forget">
4       <participant ref="bravo" />
5       <participant ref="charly" />
6     </concurrence>
7     <participant ref="delta" />
8   </sequence>

The attributes “count” and “remaining” state that the concurrence is over when 1 branch has replied and that remaining branches should simply get forgotten (their reply will simply get discarded).

 1   sequence do
 2     alpha
 3     concurrence :count => "2", :remaining => "forget" do
 4       bravo
 5       charly
 6       delta
 7     end
 8     echo
 9   end

In this example expressed in Ruby, as soon as two branches replied, the flow will resume to ‘echo’.

original pattern explanation


Structural Patterns

Arbitrary Cycles

The ability to represent cycles in a process model that have more than one entry or exit point.

The cursor expression expression is used for implementing that pattern. It obeys to the “jump”, “skip” and “back” commands.

The process represented as flash animation on the Workflow Patterns site may be implemented as :

 1   <cursor>
 2     <participant ref="alpha" />
 3     <participant ref="bravo" />
 4     <participant ref="charly" />
 5     <if test="${condition}">
 6       <back step="2" />
 7     </if>
 8     <participant ref="delta" />
 9     <if test="${condition}">
10       <back step="3" />
11     </if>
12     <participant ref="echo" />
13   </cursor>

but that doesn’t put any emphasis on the more than one entry point aspect, this could :

 1   class Pat10Definition < OpenWFE::ProcessDefinition
 2 
 3     sequence do
 4       jump :step => "1"
 5       sub # will enter subprocess 'sub' at participant 'bravo'
 6 
 7       jump :step => "0"
 8       sub # will enter subprocess 'sub' at participant 'alpha' (useless)
 9 
10       jump 2
11       sub # will enter subprocess 'sub' at participant 'charly'
12 
13       #
14       # as jump is used in a sequence, it has no direct effect, but as soon
15       # as a cursor spots it (here, when entering the 'sub'), the jump
16       # is performed
17     end
18 
19     process_definition :name => "sub" do
20       cursor do
21         participant "alpha"
22         participant "bravo"
23         participant "charly"
24       end
25     end
26   end

that could be refined into

 1   class Pat10Definition < OpenWFE::ProcessDefinition
 2 
 3     sequence do
 4       sub :step => "1"
 5       sub :step => "0"
 6       sub :step => "2"
 7     end
 8 
 9     process_definition :name => "sub" do
10       sequence do
11         jump "${step}"
12         cursor do
13           participant "alpha"
14           participant "bravo"
15           participant "charly"
16         end
17       end
18     end
19   end

(subprocess call attributes get saved as variables…)

original pattern explanation

Implicit Termination

Replicating the flash animation of the pattern, we obtain :

 1   <sequence>
 2     <participant ref="alpha" />
 3     <concurrence>
 4       <sequence>
 5         <participant ref="bravo" />
 6         <participant ref="delta" />
 7       </sequence>
 8       <sequence>
 9         <participant ref="charly" />
10         <participant ref="echo" />
11       </sequence>
12     <concurrence/>
13   </sequence>

Our ‘implicit termination’ sits at the end of the concurrence and the sequence.

With a Ruby process definition, it would look like :

 1   sequence do
 2     alpha
 3     concurrence do
 4       sequence do
 5         bravo
 6         delta
 7       end
 8       sequence do
 9         charly
10         echo
11       end
12     end
13   end

One should be able to write Ruote process definitions without using an “explicit termination sink”.

original pattern explanation


Multiple Instance Patterns

Multiple instances without synchronization

The description says : “within a given process instance, multiple instances of an activity can be created. These instances are independent of each other and run concurrently. There is no requirement to synchronize them upon completion.”

 1   <sequence>
 2     <participant ref="alpha" />
 3     <concurrent-iterator on-field="count" to-field="f" >
 4       <forget>
 5         <participant ref="bravo" />
 6       </forget>
 7     </concurrent-iterator>
 8     <participant ref="charly" />
 9   </sequence>

With a Ruby process definition, that would look like :

 1   sequence do
 2     alpha
 3     concurrent_iterator :on_field => "count", :to_field => "f" do
 4       forget do
 5         bravo
 6       end
 7     end
 8     charly
 9   end

The participant bravo is wrapped within a ‘forget’ expression as “there is no requirement to synchronize them upon completion”. The flow will continue directly to participant “charly”.

original pattern explanation

Multiple instances with a priori design-time knowledge

“the required number of instances is known at design time. These instances are independent of each other and run concurrently. It is necessary to syncrhonize the activity instances at completion before any subsequent activities can be triggered.”

A ‘plain’ implementation of that might be :

 1   <sequence>
 2     <participant ref="alpha" />
 3     <concurrence>
 4       <participant ref="bravo" />
 5       <participant ref="bravo" />
 6       <participant ref="bravo" />
 7     </concurrence>
 8     <participant ref="charly" />
 9   </sequence>

We know at design time that the activity / participant ‘bravo’ has to be run 3 times concurrently.

A bit lighter :

1   <sequence>
2     <participant ref="alpha" />
3     <concurrent-iterator on-value="a, b, c" to-field="index">
4       <participant ref="bravo" />
5     </concurrent-iterator>
6     <participant ref="charly" />
7   </sequence>

With a Ruby process definition it would look like :

 1 require 'openwfe/def'
 2 
 3 class Pattern13 < OpenWFE::ProcessDefinition
 4   sequence do
 5     participant :ref => "alpha"
 6     concurrent_iterator :on_value => "a, b, c", :to_field => "index"
 7       participant :ref => "bravo"
 8     end
 9     participant :ref => "charly"
10   end
11 end

original pattern explanation

Multiple instances with a priori run-time knowledge

“Within a given process instance, multiple instances of an activity can be created. The required number of instances may depend on a number of runtime factors, including state data, resource availability and inter-process communications, but is known before the activity instances must be created. Once initiated, these instances are independent of each other and run concurrently. It is necessary to synchronize the instances at completion before any subsequent activities can be triggered.”

It’s again a job for a “concurrent-iterator”. In this example, a comma separated list of activities in kept in the variable named “activity_list” (maybe something like “sort, wash, dry”). The iterated value, stored in the variable “index” is passed to each ‘activity’ participant as a task parameter.

1   <concurrent-iterator on-variable-value="activity_list" to-variable="index">
2     <participant ref="machine" task="${index}" />
3   </concurrent-iterator>
1   concurrent_iterator :on-variable-value => "activity_list", :to-variable => "index" do
2     participant :ref => "machine", :task => "${index}"
3   end

original pattern explanation

Multiple instances without a priori run-time knowledge

Basically the number of activity is known at runtime and may change at any point during the execution of these activities.

A loop or a cursor could be used to implement that pattern :

1   <loop>
2     <concurrent-iterator on-variable-value="activity_list" to-variable="index">
3       <participant ref="machine" task="${index}" />
4     </concurrent-iterator>
5     <break if="${activity_list} == ''" />
6   </loop>

But it doesn’t quite reflect the “at any time” requirement. Maybe something like that :

 1   loop do
 2     concurrence :merge-type => "mix", :merge => "lowest" do
 3       concurrent_iterator :on_value => "${activity_list}", :to_variable => "index" do
 4         participant :ref => "machine", :task => "${index}"
 5       end
 6       participant :ref => "decision_taker"
 7     end
 8     _break :if => "${activity_list} == ''"
 9   end

This implementation (as a Ruby process definition) assumes that the activity list will be filled by the participant “decision_taker” (which is given a priority (it’s the lowest child) in the concurrence mix merge).

original pattern explanation


State-based Patterns

Deferred Choice

This pattern is not directly supported by Ruote (OpenWFEru), as it require a better control of the state of the task (workitems). The Ruote engine pushes the workitems towards the participants but does enforce any particular workitem state (offered, started, terminated, …), it considers that the job of the participant implementation.

1   <concurrence
2     count="1"
3     remaining="cancel"
4   >
5     <participant ref="an-activity" />
6     <participant ref="another-activity" />
7   </concurrence>

(‘cancel’ is the default behaviour for a concurrence with a ‘count’).

As soon as one of the activities is over, the flow resumes (after the concurrence) and the other activity gets cancelled.

1   concurrence :count => 2 do
2     subprocess_0
3     subprocess_1
4     subprocess_2
5   end

In that Ruby process definition example, as soon as two subprocesses do reply to the concurrence, the other gets cancelled and the flow resumes.

concurrence expression
original pattern explanation

Interleaved Parallel Routing

OpenWFEja has an “interleaved” expression supposed to directly implement this pattern. Ruote doesn’t reproduce it but makes use of the “reserve” expression (also used for “critical section”).

 1  <concurrence>
 2    <reserve mutex="toto">
 3      <participant ref="test_b" />
 4    </reserve>
 5    <sequence>
 6      <reserve mutex="toto">
 7        <participant ref="test_c" />
 8      </reserve>
 9      <reserve mutex="toto">
10        <participant ref="test_d" />
11      </reserve>
12    </sequence>
13  </concurrence>

The ‘reserve’ expression is a named mutex, the name is a variable name. Thus if the name begins with ‘//’ (double-slash), the mutex will be stored at the engine level and the ‘critical section’ may span multiple business processes. The examples here have their scope limited to their process. A mutex name prefixed with ‘/’ (one slash) indicates that the variable should be bound at process level, this is useful for mutexes shared among subprocesses (not putting a slash prefix would create mutex locally / at the subprocess level, defeating the exclusivity purpose).

 1  concurrence do
 2    reserve :mutex => :toto do
 3      test_b
 4    end
 5    sequence do
 6      reserve :mutex => :toto do
 7        test_c
 8      end
 9      reserve :mutex => :toto do
10        test_d
11      end
12    end
13  end

reserve expression
original pattern explanation

Milestone

This pattern is about enabling activities only when the process reaches a certain state.

 1   concurrence do
 2     sequence do
 3       participant :ref => "A"
 4       participant :ref => "B", :tag => "milestone"
 5       participant :ref => "C"
 6     end
 7     sequence do
 8       wait :until => "'${milestone}' != ''", :frequency => "300"
 9       participant :ref => "D"
10     end
11   end

In this segment of process, the participant ‘D’ will be activated (applied) only when the parallel branch reaches participant ‘B’.

This pattern is implemented by relying on the :tag attribute that any Ruote expression may yield (more about those tags in this ‘state machine’ blog post).

When the tag “milestone” gets set, the wait expression’s condition will evaluate to true and the lower parallel branch will resume its execution and reach participant ‘D’.

Since Ruote 0.9.17, the condition can be expressed as :

1 
2   #wait :until => "'${milestone}' != ''", :frequency => "300"
3     # before Ruote (OpenWFEru) 0.9.17
4 
5   wait :until => "${milestone} is set", :frequency => "300"
6     # since Ruote 0.9.17

To conclude with an XML example, the ‘when’ expression can be used as well :

 1   <concurrence>
 2     <sequence>
 3       <participant ref="A" />
 4       <sequence tag="milestone">
 5         <participant ref="B" />
 6         <participant ref="C" />
 7       </sequence>
 8       <participant ref="D" />
 9     </sequence>
10     <when test="'${milestone}' != ''">
11       <participant ref="E" />
12     </when>
13   </concurrence>

Here, the participant ‘E’ is ‘activated’ when the sequence ‘B – C’ is reached (in the previous (Ruby) example, a single participant was tagged ‘milestone’).

Of course, tag names other than ‘milestone’ can be used.

wait expression
when expression
original pattern explanation


Cancellation Patterns

Cancel Task

(work in progress)

Cancel Case

(work in progress)


New Control Flow Patterns