This document describe Flor implementation for each of the Control-Flow Patterns presented by the Workflow Patterns website which catalog a comprehensive list of those workflow building blocks. Each implementation are provided with a link to the original pattern explanation and flash animation.
This is a self-evaluation. For an authoritative source, see the workflow patterns website and its mailing list.
If you disagree with a solution proposed here, you are much welcome to raise an issue pointing at why the solution doesn't match and potentially include a better solution.
- Multi-Choice
- Structured Synchronizing Merge
- Multi-Merge
- Structured Discriminator
- Blocking Discriminator
- Cancelling Discriminator
- Structured Partial Join
- Blocking Partial Join
- Cancelling Partial Join
- Generalised AND-Join
- Local Synchronizing Merge
- General Synchronizing Merge
- Thread Merge
- Thread Split
- Multiple Instances without Synchronization
- Multiple Instances with a Priori Design-Time Knowledge
- Multiple Instances with a Priori Run-Time Knowledge
- Multiple Instances without a Priori Run-Time Knowledge
- Static Partial Join for Multiple Instances
- Cancelling Partial Join for Multiple Instances
- Dynamic Partial Join for Multiple Instances
- Deferred Choice
- Interleaved Parallel Routing
- Milestone
- Critical Section
- Interleaved Routing
- Cancel Task
- Cancel Case
- Cancel Region
- Cancel Multiple Instance Activity
- Complete Multiple Instance Activity
- Arbitrary Cycles
- Structured Loop
- Recursion
- Implicit Termination
- Explicit Termination
- Transient Trigger
- Persistent Trigger
Chaining activities in sequence.
sequence
task 'alpha'
task 'bravo'
wp/explanation | wp/animation | top
The concurrence is the main tool for the parallel split.
concurrence
#
# alpha and bravo will be tasked concurrently
#
task 'alpha'
task 'bravo'
wp/explanation | wp/animation | top
The concurrence by waiting (by default) for all its children to reply is the simplest flor synchronization tool.
sequence
task 'alpha'
concurrence
task 'bravo'
task 'charly'
task 'delta' # task 'delta' will be reached once 'bravo' and 'charly' both have replied
wp/explanation | wp/animation | top
The simplest flor procedure to use to support this pattern is if.
sequence
# ...
if
f.customer.age > 21 # condition
sequence # then
set f.customer.type 'adult'
order_kit _
sequence # else
set f.customer.type 'non-adult'
order_teenager_kit _
# ...
The case and match are the other two contenders for exclusive choice.
wp/explanation | wp/animation | top
A simple merge occur when two (or more) exclusive branch converge. As seen in exclusive choice this pattern is implicitly supported. It simply occurs when the ‘then’ or the ‘else’ clause of an if terminates and the flow resumes.
wp/explanation | wp/animation | top
concurrence and if can be combined to support this workflow control pattern.
sequence
# ...
concurrence
if f.traffic or f.crime
task "despatch police"
if f.fire
task "despatch fire engine and ambulance"
if f.wounded
task "despatch ambulance"
# ...
The concurrence may result in 0 to 3 tasks being "emitted", in case of 0, the flow will immediately resume after the concurrence. Else, the flow will wait until the 1 to 3 tasks have completed.
wp/explanation | wp/animation | top
The explanation for this pattern sports an example stating:
Depending on the type of emergency, either or both of the despatch-police and despatch-ambulance tasks are initiated simultaneously. When all emergency vehicles arrive at the accident, the transfer-patient task commences.
Here is a naive flor translation (see previous pattern for its origin):
sequence
# ...
concurrence
task "despatch police" if f.traffic or f.crime
task "despatch ambulance" if f.wounded
task "transfer-patient"
# ...
The concurrence procedure, by default, waits for all its children to respond before ending and replying to its parent procedure (here a sequence. The sequence then hands the flow to the task "transfer-patient"
followup.
wp/explanation | wp/animation | top
Here is the example given for this merge:
The lay_foundations, order_materials and book_labourer tasks occur in parallel as separate process branches. After each of them completes the quality_review task is run before that branch of the process finishes.
Here is a naive flor translation (tasks are expressed directly, so "lay_foundations" could refer on the tasker "lay_foundations" or the function "lay_foundations", depending on your setting:
sequence
# ...
concurrence
lay_foundations _
order_materials _
book_labourer _
quality_review _
# ...
But the "the quality_review task is run before that branch (...) finishes" is not respected. This is better, but verbose:
sequence
# ...
concurrence
sequence
lay_foundations _
quality_review _
sequence
order_materials _
quality_review _
sequence
book_labourer _
quality_review _
# ...
This uses a wrapper function, it calls it with a block (like a Ruby block):
sequence
# ...
define with_quality_review
yield _ # the wrapped block is called
quality_review _ # then the review is performed
concurrence
with_quality_review
lay_foundations _
with_quality_review
order_materials _
with_quality_review
book_labourer _
TODO: alternatives...
wp/explanation | wp/animation | top
Here is the example given for this merge:
When handling a cardiac arrest, the check_breathing and check_pulse tasks run in parallel. Once the first of these has completed, the triage task is commenced. Completion of the other task is ignored and does not result in a second instance of the triage task.
sequence
concurrence expect: 1 remaining: 'forget'
check_breathing _
check_pulse _
triage _
The concurrence expects one reply and then forgets the remaining branch.
wp/explanation | wp/animation | top
The example given for this merge:
After the extract-sample task has completed, parts of the sample are sent to three distinct laboratories for examination. Once the first of these laboratories completes the sample-analysis, the other two task instances are cancelled and the review-drilling task commences.
sequence
extract_sample _
concurrence expect: 1 remaining: 'cancel'
examine_sample 'laboratory 1'
examine_sample 'laboratory 2'
examine_sample 'laboratory 3'
drill_review _
The concurrence expects one reply and then cancels the remaining branches.
Here is a variant using c-each:
sequence
set labs [ 'lab alpha', 'lab biometa', 'lab cruz' ]
# ...
extract_sample _
c-each labs expect: 1 remaining: 'cancel'
examine_sample lab: elt
drill_review _
wp/explanation | wp/animation | top
A task is only enabled when in a specific state (typically a parallel branch).
Flor's workflow definition might be paraphrased as: "E is tasked only if the execution has the tag 'bravo'":
concurrence
sequence
task 'A'
task 'B' tag: 'bravo'
task 'C'
sequence
task 'D'
task 'E' if tag.bravo
task 'F'
The predecessor to Flor (Ruote) was proposing a syntax a bit more convoluted.