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 OpenWFEru.
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
Advanced Branching and Synchronization Patterns
Structural Patterns
Multiple Instance Patterns
State-based Patterns
Cancellation Patterns
New Control Flow Patterns
<sequence>
<participant ref="alpha" />
<participant ref="bravo" />
</sequence>
sequence do
alpha
bravo
end
(OpenWFEru finding no expression and no subprocess definition for ‘alpha’ and ‘bravo’, it automatically maps them to their participants).
What else is there to say ?
Splitting the process instance into two parallel path of execution.
<concurrence>
<participant ref="alpha" />
<participant ref="bravo" />
</concurrence>
concurrence do
alpha
bravo
end
concurrence expression original pattern explanation
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 ‘routing’ within the process : the flow will go one way or the other, but not both.
Two expressions in OpenWFEru are used to implement this pattern : ‘if’ and ‘case’.
<if>
<equals field-value="x" other-value="y" />
<!-- then -->
<participant ref="theodor" />
<!-- else -->
<participant ref="emma" />
</if>
_if do
equals :field_value => :x, :other-value => "y"
# then
participant :theodor
# else
participant :emma
end
(not to collide with Ruby’s ‘if’, OpenWFEru’s ‘if’ is escaped with a ”_” prefix in the Ruby process definition, same thing for ‘case’)
<case>
<equals field-value="x" other-value="y" />
<participant ref="alpha" />
<if test="${field:price} > 12.0" />
<participant ref="bravo" />
<!-- optional else -->
<participant ref="emma" />
</case>
(note here the usage of ‘if’ without children as a simple condition expression)
_case do
equals :field_value => "x", :other_value => "y"
alpha
_if :test => "${field:price} > 12.0"
bravo
# optional else :
emma
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
The ‘simple merge’ is implicitely supported by the ‘if’ and ‘case’ expressions.
if expression case expression original pattern explanation
The straightforward way for implementing this pattern with OpenWFE[ru] is to use a set of ‘if’ nested within a ‘concurrence’ :
<concurrence>
<if test="${field:price} > 12.0" >
<participant ref="alpha" />
</if>
<if>
<equals field="price" value="0">
<participant ref="bravo" />
</if>
<if test="${f:price} > 100.0" >
<participant ref="charly" />
</if>
</concurrence>
Not that if the price is superior to 100, both the participant alpha and the participant charly will ‘receive the flow’.
concurrence do
_if :test => "${field:price} > 12.0" do
participant :alpha
end
_if do
_equals :field => "price", :value => "0"
participant :ref => :bravo
end
_if :test => "${f:price} > 100.0" do
participant :ref => "charly"
end
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 :
concurrence do
alpha :if => "${f:price} > 12.0"
bravo :if => "${f:price} == 0"
charly :if => "${f:price} > 100.0"
end
(both the ‘participant’ and the ‘subprocess’ support this ‘if’ (and ‘unless’) attribute.
if expression concurrence expression original pattern explanation
The OpenWFEru 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
This pattern is very interesting, it shows the difference between a graph-based workflow engine and OpenWFEru.
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 OpenWFE[ru] :
<process-definition name="pat8multimerge" revision="2">
<concurrence>
<if test="${field:price} > 12.0" >
<sequence>
<participant ref="alpha" />
<after/>
</sequence>
</if>
<if>
<equals field="price" value="0">
<sequence>
<participant ref="bravo" />
<after/>
</sequence>
</if>
<if test="${f:price} > 100.0" >
<sequence>
<participant ref="charly" />
<after/>
</sequence>
</if>
</concurrence>
<process-definition name="after">
<sequence>
<participant ref="delta" />
<participant ref="echo" />
</sequence>
</process-definition>
</process-definition>
Each concurrent workitem will make its way to its own instance of the ‘after’ subprocess.
require 'openwfe/def'
class Pat8MultiMerge2 < OpenWFE::ProcessDefinition
concurrence do
_if :test => "${field:price} > 12.0" do
sequence do
alpha
after
end
end
_if do
equals :field => "price", :value => "0"
sequence do
bravo
after
end
end
_if :test => "${f:price} > 100.0" do
sequence do
charly
after
end
end
end
process_definition :name => "after" do
sequence do
delta
echo
end
end
end
As soon as a concurrent branch terminates, the concurrence expression resumes the flow.
<sequence>
<participant ref="alpha" />
<concurrence count="1" remaining="forget">
<participant ref="bravo" />
<participant ref="charly" />
</concurrence>
<participant ref="delta" />
</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).
sequence do
alpha
concurrence :count => "2", :remaining => "forget" do
bravo
charly
delta
end
echo
end
In this example expressed in Ruby, as soon as two branches replied, the flow will resume to ‘echo’.
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 :
<cursor>
<participant ref="alpha" />
<participant ref="bravo" />
<participant ref="charly" />
<if test="${condition}">
<back step="2" />
</if>
<participant ref="delta" />
<if test="${condition}">
<back step="3" />
</if>
<participant ref="echo" />
</cursor>
but that doesn’t put any emphasis on the more than one entry point aspect, this could :
class Pat10Definition < OpenWFE::ProcessDefinition
sequence do
jump :step => "1"
sub # will enter subprocess 'sub' at participant 'bravo'
jump :step => "0"
sub # will enter subprocess 'sub' at participant 'alpha' (useless)
jump 2
sub # will enter subprocess 'sub' at participant 'charly'
#
# as jump is used in a sequence, it has no direct effect, but as soon
# as a cursor spots it (here, when entering the 'sub'), the jump
# is performed
end
process_definition :name => "sub" do
cursor do
participant "alpha"
participant "bravo"
participant "charly"
end
end
end
that could be refined into
class Pat10Definition < OpenWFE::ProcessDefinition
sequence do
sub :step => "1"
sub :step => "0"
sub :step => "2"
end
process_definition :name => "sub" do
sequence do
jump "${step}"
cursor do
participant "alpha"
participant "bravo"
participant "charly"
end
end
end
end
(subprocess call attributes get saved as variables…)
Replicating the flash animation of the pattern, we obtain :
<sequence>
<participant ref="alpha" />
<concurrence>
<sequence>
<participant ref="bravo" />
<participant ref="delta" />
</sequence>
<sequence>
<participant ref="charly" />
<participant ref="echo" />
</sequence>
<concurrence/>
</sequence>
Our ‘implicit termination’ sits at the end of the concurrence and the sequence.
With a Ruby process definition, it would look like :
sequence do
alpha
concurrence do
sequence do
bravo
delta
end
sequence do
charly
echo
end
end
end
One should be able to write OpenWFE[ru] process definitions without using an “explicit termination sink”.
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.”
<sequence>
<participant ref="alpha" />
<concurrent-iterator on-field="count" to-field="f" >
<forget>
<participant ref="bravo" />
</forget>
</concurrent-iterator>
<participant ref="charly" />
</sequence>
With a Ruby process definition, that would look like :
sequence do
alpha
concurrent_iterator :on_field => "count", :to_field => "f" do
forget do
bravo
end
end
charly
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”.
“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 :
<sequence>
<participant ref="alpha" />
<concurrence>
<participant ref="bravo" />
<participant ref="bravo" />
<participant ref="bravo" />
</concurrence>
<participant ref="charly" />
</sequence>
We know at design time that the activity / participant ‘bravo’ has to be run 3 times concurrently.
A bit lighter :
<sequence>
<participant ref="alpha" />
<concurrent-iterator on-value="a, b, c" to-field="index">
<participant ref="bravo" />
</concurrent-iterator>
<participant ref="charly" />
</sequence>
With a Ruby process definition it would look like :
require 'openwfe/def'
class Pattern13 < OpenWFE::ProcessDefinition
sequence do
participant :ref => "alpha"
concurrent_iterator :on_value => "a, b, c", :to_field => "index"
participant :ref => "bravo"
end
participant :ref => "charly"
end
end
“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.
<concurrent-iterator on-variable-value="activity_list" to-variable="index">
<participant ref="machine" task="${index}" />
</concurrent-iterator>
concurrent_iterator :on-variable-value => "activity_list", :to-variable => "index" do
participant :ref => "machine", :task => "${index}"
end
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 :
<loop>
<concurrent-iterator on-variable-value="activity_list" to-variable="index">
<participant ref="machine" task="${index}" />
</concurrent-iterator>
<break if="${activity_list} == ''" />
</loop>
But it doesn’t quite reflect the “at any time” requirement. Maybe something like that :
loop do
concurrence :merge-type => "mix", :merge => "lowest" do
concurrent_iterator :on_value => "${activity_list}", :to_variable => "index" do
participant :ref => "machine", :task => "${index}"
end
participant :ref => "decision_taker"
end
_break :if => "${activity_list} == ''"
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).
<concurrence
count="1"
remaining="cancel"
>
<participant ref="an-activity" />
<participant ref="another-activity" />
</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.
concurrence :count => 2 do
subprocess_0
subprocess_1
subprocess_2
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
OpenWFEja has an “interleaved” expression supposed to directly implement this pattern. OpenWFEru doesn’t reproduce it but makes use of the “reserve” expression (also used for “critical section”).
<concurrence>
<reserve mutex="toto">
<participant ref="test_b" />
</reserve>
<sequence>
<reserve mutex="toto">
<participant ref="test_c" />
</reserve>
<reserve mutex="toto">
<participant ref="test_d" />
</reserve>
</sequence>
</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).
concurrence do
reserve :mutex => :toto do
test_b
end
sequence do
reserve :mutex => :toto do
test_c
end
reserve :mutex => :toto do
test_d
end
end
end
reserve expression original pattern explanation
This pattern is about enabling activities only when the process reaches a certain state.
concurrence do
sequence do
participant :ref => "A"
participant :ref => "B", :tag => "milestone"
participant :ref => "C"
end
sequence do
wait :until => "'${milestone}' != ''", :frequency => "300"
participant :ref => "D"
end
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 OpenWFEru 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 OpenWFEru 0.9.17, the condition can be expressed as :
#wait :until => "'${milestone}' != ''", :frequency => "300"
# before OpenWFEru 0.9.17
wait :until => "${milestone} is set", :frequency => "300"
# since OpenWFEru 0.9.17
To conclude with an XML example, the ‘when’ expression can be used as well :
<concurrence>
<sequence>
<participant ref="A" />
<sequence tag="milestone">
<participant ref="B" />
<participant ref="C" />
</sequence>
<participant ref="D" />
</sequence>
<when test="'${milestone}' != ''">
<participant ref="E" />
</when>
</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
(work in progress)