ruote participants

Usually, a process definition weaves the trajectory of workitems among participants.

1   <process-definition name="def0" revision="0.1">
2     <sequence>
3       <participant ref="accounting" />
4         <!-- send invoice -->
5       <participant ref="logistics" />
6         <!-- deliver items -->
7     </sequence>
8   </process-definition>

(note : this is not the only way to define processes)

Participants are invoked with the participant expression.

There are basically two different kind of participant, ‘local’ and ‘remote’ (or maybe ‘proxy’). Find there some notes about writing custom participants as well.

Participants are registered in the engine via the “register_participant” method of the engine :

 1   require 'openwfe/engine/engine'
 2   engine = Engine.new
 3 
 4   require 'openwfe/participants/store_participants'
 5   hpAlice = HashParticipant.new
 6 
 7   hpBob = HashParticipant.new
 8 
 9   engine.register_participant(:alice, hpAlice)
10   engine.register_participant(:bob, hpBob)
11 
12   # ...

To remove participants from the engine :

1   engine.unregister_participant(:alice)
2   engine.unregister_participant("bob")

Participants can be categorized in two flavours : push and pull. Push participants like for example a MailParticipant or the BlockParticipant react immediately upon receiving a workitem (sending an email, executing some code).
Pull participants like store participants, simply keep the workitems for later consumption by external services.

 

Here are the participant implementations that ship with Ruote (OpenWFEru) :

 

Their descriptions :

ActiveParticipant

It’s too bad this participant comes first in this [alphabetical] list. It’s a bit advanced, and has been deprecated by the ArParticipant.

The active page is dedicated to the ActiveRecord items in Ruote.

rdoc

ActiveStoreParticipant

It’s a refinement of the ActiveParticipant. This is the participant used by ‘densha’ for its workitem stores. Never releases of ruote-web2 uses the new ArParticipant, which has deprecated the ActiveStoreParticipant.

rdoc

ActiveResourceParticipant

Ruby on Rails provides a REST oriented framework for interacting with its active record, it’s called Active Resource. Torsten Schoenebaum has written a participant that wraps HTTP interactions with Rails [active] resource.

1   engine.register_participant(
2     'reminder',
3     ActiveResourceParticipant.new(
4       :site => 'http://localhost:3000',
5       :resource_name => 'invoice',
6       :method => 'post',
7       :argument => 'invoice_details'))

This example will post (create) a new invoice based on the information (array) found in the field ‘invoice_details’ of incoming workitems.

rdoc

AliasParticipant

Given a list of ‘user participant’ :

 1   engine.register_participant :alice do |workitem|
 2     workitem.whatever = "oh and by the way"
 3   end
 4   engine.register_participant :bob do |workitem|
 5     workitem.decision = "there is a coordination problem, let's decide later"
 6   end
 7   engine.register_participant :charly do |workitem|
 8     workitem.time_estimate = "Carlito says : cuando la vaca vuela"
 9   end

Some aliases may get helpful :

1   engine.register_participant :boss, AliasParticipant.new("bob")
2   engine.register_participant :hr_head, AliasParticipant.new("alice")
3   engine.register_participant "engineering_.*", AliasParticipant.new("charly")

rdoc

AtomFeedParticipant

More at atom :: AtomFeedParticipant

rdoc

ArParticipant

The preferred participant for storing workitems in ActiveRecord. It was developed for ruote-web2 and is also the new default participant for ruote-rest from version 0.9.21. This participant deprecates the ActiveParticipant and ActiveStoreParticipant.

To migrate from the ActiveParticipant to the new ArParticipant you use the arparticipants.rb script.

The active page is dedicated to the ActiveRecord items in Ruote.

rdoc

AtomPubParticipant

More at atom :: AtomPubParticipant

rdoc

BlockParticipant

The participant with the most Ruby-feel.

The idea is to directly map a Ruby block as a participant to a workflow.

1   engine.register_participant(:vat) do |workitem|
2     price = Integer(workitem.price)
3     price = price + price * $vat
4     workitem.net_price = price
5   end

The reply to the engine is implicit, it has not to be stated in the block.

This participant is used in one of the examples in the examples/ folder of Ruote.
In that example, it’s barely used to dump the incoming workitem to the standard output (stdout).

You can pass a block that accepts no arguments, one (workitem) or two (flow expression and workitem) to this participant.

The last value in the block (its return value) is set as the value of the field ‘result’ of the outgoing workitem. Beware : do not set this value to something not serializable (like a reference to a complex object or something like that).

 1   engine.register_participant(:my_participant) do |flow_expression, workitem|
 2     workitem.attributes["parent"] = flow_expression.parent_id.to_s
 3       #
 4       # just storing the parent expression of the participant expression
 5       # that sent the workitem, as a string in the workitem
 6       #
 7       # you could do more complicated and dangerous things with
 8       # handle on the flow expression (but that's an advanced topic)
 9   end

Note that if the block’s execution could take a certain amount of time, it’d be better to provide error handling and CancelItem handling inside of it. A short and hypothetical example :

 1   engine.register_participant "sluggy" do |workitem|
 2   
 3     if workitem.is_a?(OpenWFE::CancelItem)
 4       #
 5       # cancel job 
 6 
 7       job_id = jobs[workitem.fei]
 8 
 9       external_sluggy_system.cancel_job(job_id)
10     else
11       #
12       # launch job
13 
14       job_id = external_sluggy_system.execute_job(
15         workitem.param0, workitem.param1)
16 
17       jobs[workitem.fei] = job_id
18     end
19   end

Maybe in such a case, a dedicated participant class could be better, leaving BlockParticipant for tiny chunk of ruby code.

rdoc

BlogParticipant

More at atom :: BlogParticipant

rdoc

CsvParticipant

CsvParticipants are called “decision tables” or “decision participants” in Java OpenWFE.

Here is a blog entry about the CSV tables behind decision participants.

Note that since Ruote 0.9.13, the CSVParticipant has been moved to the OpenWFE::Extras namespace.

rdoc

rdoc (CSV table)

FileParticipant

This participant just dumps workitems to files. By default, it dumps the workitem as a YAML string, but you can override the encode_workitem() method.

1   require 'openwfe/participants'
2 
3   engine.register_participant("Fedor", OpenWFE::FileParticipant)

In this example, workitems for participant ‘Fedor’ will get dispatched (dumped) in the directory ./work/out/

rdoc

The FileParticipant has a counterpart : the FileListener which polls directory for incoming ‘file’ workitems.

The FileParticipant alone is interesting as well for debugging a business process, it can be placed at special places to “dump” the workitem as it reaches those points.

HashParticipant

It directly maps a participant with a hash for storing workitems waiting for treatment.

This participant is used in one of the examples in the examples/ folder of Ruote.

rdoc

But the better rdoc to browse is the one of the StoreParticipantMixin module HashParticipant implements.

JabberParticipant

Kenneth Kalmer implemented this participant for his ruote engines to communicate with agents over XMPP (Jabber).

1   engine.register_participant(
2     :jabber,
3     OpenWFE::Extras::JabberParticipant.new(
4       :jabber_id => 'ruote@devbox',
5       :password => 'secret',
6       :contacts => [ 'kenneth@devbox', 'torsten@devbox' ]))

rdoc

Kenneth also contributed a JabberListener for Ruote to listen to message (workitems) coming over XMPP (rdoc)

MailParticipant

This participant sends a notification per email. The text of the email is generated by filling a template with the data found in the workitem payload.

(Thanks to Alain Hoang for the initial implementation of this participant)

rdoc

rdoc (a former version of the MailParticipant)

NoOperationParticipant

Immediately replies to the engine, doesn’t touch the workitem (no operation).

rdoc

NullParticipant

Discards the workitem (no operation), but doesn’t reply to the engine. A bit like a BPM /dev/null

rdoc

ProcessParticipant

The classical way to call a participant is via the subprocess :

 1   <sequence>
 2     <participant ref="alice" />
 3     <subprocess ref="review" />
 4     <participant ref="bob" />
 5     <subprocess ref="review" />
 6     <subprocess ref="http://process.server.company.org/processes/proc0.xml" />
 7   </sequence>
 8 
 9   <process-definition name="review">
10     <loop>
11       <participant ref="boss1" />
12       <participant ref="boss2" />
13       <break if="${f:boss2_decision} == ok" />
14     </loop>
15   </process-definition>

With a Ruby process definition it would look like :

 1     sequence do
 2       alice
 3       review
 4       bob
 5       review
 6       subprocess :ref => "http://process.server.company.org/processes/proc0.xml"
 7     end
 8 
 9     process_definition :name => "review" do
10       _loop do
11         boss1
12         boss2
13         _break :if => "${f:boss2_decision} == ok"
14       end
15     end

Note that the participant and the subprocess are simply referred directly via their names, except for the subprocess call using an URL. (Note too that expressions whose names are Ruby keywords like ‘loop’ and ‘break’ are escaped with an underscore as a prefix.

A ProcessParticipant can solve this URL problem :

1   require 'openwfe/participants'
2 
3   engine.register_participant(
4     "proc0", 
5     ProcessParticipant.new("http://process.server.company.org/processes/proc0.xml"))

The process definition body would then look like :

1   sequence do
2     alice
3     review
4     bob
5     review
6     proc0
7   end

rdoc for more information on how to register a participant (as a URL, as a Ruby process class, …)

SoapParticipant

Wraps a call to a webservice within a participant. The ‘result’ field of the workitem receives then the result of the call.

This is a minimal implementation, but it’s Ruby, the extension possibilities are huge.

rdoc

This participant is used in one of the examples in the examples/ folder of Ruote.

SqsParticipant

SQS is an Amazon web service : ‘Amazon Simple Queue Service (Amazon SQS) offers a reliable, highly scalable hosted queue for storing messages as they travel between computers’, learn more at http://aws.amazon.com

The SqsParticipant deposits workitems on a SQS queue where they can be read by other applications.

1   require 'openwfe/extras/participants/sqs_participants'
2 
3   engine.register_participant(:queue, OpenWFE::Extras::SqsParticipant.new("workqueue0"))

By default, this participant does a YAML dump of the workitem in its ‘hash version’ and then encodes it as Base64 before putting it in the queue.
You can very easily change that by overriding encode_workitem(), for example :

 1   require 'openwfe/extras/participants/sqs_participants'
 2 
 3   sp = OpenWFE::Extras::SqsParticipant.new("workqueue0")
 4   class << sp
 5     #
 6     # instead of extending SqsParticipant, we simply modify the 
 7     # method implementation just for that sp instance
 8     #
 9     def encode_workitem (wi)
10       msg = ""
11       msg << wi.fei.to_s
12       msg << "\n"
13       msg << wi.attributes["task_description"]
14       msg << "\n"
15       msg
16     end
17   end
18 
19   engine.register_participant(:queue, sp)

rdoc

There is a counterpart to the SqsParticipant : an SqsListener

1   require 'openwfe/extras/listeners/sqslisteners'
2 
3   engine.add_workitem_listener(
4     OpenWFE::Extras::SqsListener.new(:wiqueue3, engine.application_context),
5     '2m15s')
6       #
7       # will poll our SQS queue named "wiqueue3" for workitems
8       # every 2 minutes and 15 seconds

A listener allows InFlowWorkItems to resume back in their originating process instance. They also accept (if allowed) LaunchItems and launch a brand new process instance according to the info found in the LaunchItem.

The SqsListener expects by default that workitems (InFlowWorkItem and LaunchItem instances) are encoded as hashes, then dumped via YAML, then encoded with Base64 (like SqsParticipant does).

TwitterParticipant

The TwitterParticipant was first documented in this blog post on the tech blog.

Twitter notification could be a lightweight substitute for email notification.

 1   require 'openwfe/extras/participants/twitter_participants'
 2 
 3   tp = OpenWFE::Extras::TwitterParticipant.new(
 4     twitter_account, twitter_password, :no_ssl => true)
 5       #
 6       # remove this :no_ssl param if the Participant should use SSL
 7       # (as sometimes Twitter doesn't listen on SSL)
 8 
 9   engine.register_participant("twitter_notification", tp)

The TwitterParticipant, out of the box supports status and direct messages. The message itself is the value of the “twitter_message” workitem field. To send a direct message, set the “twitter_target” workitem field to a twitter login name and the twitter user will receive the message via email, thanks to Twitter (email notification anyway).

The way the message and the target are determined can be changed by overriding (or monkey patching) the extract_message() method. Here is an example :

 1   require 'openwfe/extras/participants/twitter_participants'
 2 
 3   class MyTwitterParticipant < OpenWFE::Extras
 4 
 5     #
 6     # This method is meant to return a pair login name / message.
 7     #
 8     def extract_message (workitem)
 9 
10       message = ""
11       message << "customer '#{workitem.customer_name}'"
12       message << " replied '#{workitem.customer_reply}'"
13       message << " (#{Time.now.to_s})"
14       
15       [ nil, message ]
16         #
17         # always returning nil as the twitter target, the message
18         # will thus always be a status message, not a direct message
19     end
20   end

rdoc

YamlParticipant

This participant is an extension of the HashParticipant built on top of a YAML file persistence.

By default, the workitems are stored under “./work/participants/name_of_the_participant/”

This participant is used in the sample code of the ManoTracker

rdoc

As for the HashParticipant, it’s interesting to browse the rdoc of the StoreParticipantMixin module that this participant implements.