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 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

Basic Control Flow Patterns

Sequence

    <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 ?

original pattern explanation

Parallel Split

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

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 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

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 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

Structured Synchronizing Merge

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

Multi-Merge

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

original pattern explanation

Structured Discriminator

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’.

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 :

    <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…)

original pattern explanation

Implicit Termination

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”.

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.”

    <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”.

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 :

    <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

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.

    <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

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 :

    <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).

original pattern explanation

State-based Patterns

Deferred Choice

    <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

Interleaved Parallel Routing

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

Milestone

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

Cancellation Patterns

(work in progress)

New Control Flow Patterns