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
Sequence1 <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 ?
Parallel SplitSplitting 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
SynchronizationSynchronization 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 ChoiceExclusive ‘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 MergeThe ‘simple merge’ is implicitely supported by the ‘if’ and ‘case’ expressions.
if expression
case expression
original pattern explanation
Advanced Branching and Synchronization Patterns
Multi-ChoiceThe 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 MergeThe 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-MergeThis 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
Structured DiscriminatorAs 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’.
Structural Patterns
Arbitrary CyclesThe 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…)
Implicit TerminationReplicating 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”.
Multiple Instance Patterns
Multiple instances without synchronizationThe 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”.
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
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
Multiple instances without a priori run-time knowledgeBasically 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).
State-based Patterns
Deferred ChoiceThis 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 RoutingOpenWFEja 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
MilestoneThis 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