OpenWFEru participants

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

    <process-definition name="def0" revision="0.1">
        <sequence>
            <participant ref="accounting" />
                <!-- send invoice -->
            <participant ref="logistics" />
                <!-- deliver items -->
        </sequence>
    </process-definition>

As seen, participants are invoked with the ‘participant’ expression but you can write :

    <process-definition name="def0" revision="0.1">
        <sequence>
            <accounting />
            <logistics />
        </sequence>
    </process-definition>

Provided that your participant names don’t conflict with XML.

In OpenWFEru you can define processes as Ruby classes :

    class Def0 < OpenWFE::ProcessDefinition
        sequence do
            accounting
            logistics
            #
            # sometimes you'll prefer the more explicit :
            #
            # participant :ref => "accounting"
            # participant :ref => "logistics"
        end
    end

Participants are registered in the engine in this way :

    require 'openwfe/engine/engine'
    require 'openwfe/worklist/storeparticipants'

    (...)

    engine = Engine.new

    hpAlice = HashParticipant.new
    hpBob = HashParticipant.new

    engine.register_participant(:alice, hpAlice)
    engine.register_participant(:bob, hpBob)

    (...)

To remove participants from the engine :

    engine.unregister_participant(:alice)
    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 OpenWFEru :

 

Their descriptions :

ActiveParticipant

(this participant is part of the “openwferu-extras” gem)

It’s too bad this participant comes first in this [alphabetical] list. It’s a bit advanced.

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

rdoc

ActiveStoreParticipant

(this participant is part of the “openwferu-extras” gem)

It’s a refinement of the ActiveParticipant. This is the participant used by ‘densha’ for its workitem stores.

rdoc

AliasParticipant

Given a list of ‘user participant’ :

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

Some aliases may get helpful :

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

rdoc

AtomFeedParticipant

More at atom :: AtomFeedParticipant

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.

    engine.register_participant(:vat) do |workitem|
        price = Integer(workitem.price)
        price = price + price * $vat
        workitem.net_price = price
    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 OpenWFEru. 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).

    engine.register_participant(:my_participant) do |flow_expression, workitem|
        workitem.attributes["parent"] = flow_expression.parent_id.to_s
            #
            # just storing the parent expression of the participant expression
            # that sent the workitem, as a string in the workitem
            #
            # you could do more complicated and dangerous things with
            # handle on the flow expression (but that's an advanced topic)
    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 :

    engine.register_participant "sluggy" do |workitem|
    
        if workitem.is_a?(OpenWFE::CancelItem)
            #
            # cancel job 

            job_id = jobs[workitem.fei]

            external_sluggy_system.cancel_job(job_id)
        else
            #
            # launch job

            job_id = external_sluggy_system.execute_job(
                workitem.param0, workitem.param1)

            jobs[workitem.fei] = job_id
        end
    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

(this participant is part of the “openwferu-extras” gem)

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

    require 'openwfe/participants/participants'

    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

OpenWFEja features a worklist component. OpenWFEru (0.9.3) not yet, but this ‘HashParticipant’ is the first step towards a worklist.

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

rdoc

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

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.

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 :

    <sequence>
        <participant ref="alice" />
        <subprocess ref="review" />
        <participant ref="bob" />
        <subprocess ref="review" />
        <subprocess ref="http://process.server.company.org/processes/proc0.xml" />
    </sequence>

    <process-definition name="review">
        <loop>
            <participant ref="boss1" />
            <participant ref="boss2" />
            <break if="${f:boss2_decision} == ok" />
        </loop>
    </process-definition>

With a Ruby process definition it would look like :

    sequence do
        alice
        review
        bob
        review
        subprocess :ref => "http://process.server.company.org/processes/proc0.xml"
    end

    process_definition :name => "review" do
        _loop do
            boss1
            boss2
            _break :if => "${f:boss2_decision} == ok"
        end
    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 :

    require 'openwfe/participants/participants'

    engine.register_participant(
        "proc0", 
        ProcessParticipant.new("http://process.server.company.org/processes/proc0.xml"))

The process definition body would then look like :

    sequence do
        alice
        review
        bob
        review
        proc0
    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 OpenWFEru.

SocketParticipant

A quite straightforward participant, it dumps the workitems over a TCP connection.

    require 'openwfe/participants/socketparticipants'

    sp = OpenWFE::SocketParticipant.new("localhost", 7007)

    engine.register_participant(:stuart, sp)

rdoc

An extension : XmlSocketParticipant which dispatches workitems encoded as XML (as OpenWFEja understands them).

Of course, there is a SocketListener available.

SqsParticipant

(this participant and its listener is part of the “openwferu-extras” gem)

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.

    require 'openwfe/extras/participants/sqsparticipants'

    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 :

    require 'openwfe/extras/participants/sqsparticipants'

    sp = OpenWFE::Extras::SqsParticipant.new("workqueue0")
    class << sp
        #
        # instead of extending SqsParticipant, we simply modify the 
        # method implementation just for that sp instance
        #
        def encode_workitem (wi)
            msg = ""
            msg << wi.fei.to_s
            msg << "\n"
            msg << wi.attributes["task_description"]
            msg << "\n"
            msg
        end
    end

    engine.register_participant(:queue, sp)

rdoc

There is a counterpart to the SqsParticipant : an SqsListener

    require 'openwfe/extras/listeners/sqslisteners'

    engine.add_workitem_listener(
        OpenWFE::Extras::SqsListener.new(:wiqueue3, engine.application_context),
        "2m15s")
            #
            # will poll our SQS queue named "wiqueue3" for workitems
            # 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.

    require 'openwfe/extras/participants/twitterparticipants'

    tp = OpenWFE::Extras::TwitterParticipant.new(
        twitter_account, twitter_password, :no_ssl => true)
            #
            # remove this :no_ssl param if the Participant should use SSL
            # (as sometimes Twitter doesn't listen on SSL)

    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 :

    require 'openwfe/extras/participants/twitterparticipants'

    class MyTwitterParticipant < OpenWFE::Extras

        #
        # This method is meant to return a pair login name / message.
        #
        def extract_message (workitem)

            message = ""
            message << "customer '#{workitem.customer_name}'"
            message << " replied '#{workitem.customer_reply}'"
            message << " (#{Time.now.to_s})"
            
            [ nil, message ]
                #
                # always returning nil as the twitter target, the message
                # will thus always be a status message, not a direct message
        end
    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.