Business Process Administration

Disclaimer : this page does not describe a fancy user interface for business process administration in OpenWFEru. Instead it describes the base methods for questioning the engine and managing the processes within it.

The documentation for [un]registering participants in the engine is in the participants page.

The things described here play with the engine (and its expression pool). Throughout the Ruby code examples, it is assumed that the engine is accessible from the variable named “engine”.

Workflow instances (process instances) are usually designated with a workflow instance id, which is abbreviated to wfid. One could use pid and that would really do the process engine == operating system.

Expression instances are atomic pieces of running Process instances. They are designated with a unique FlowExpressionId. Workitems are tagged with the FlowExpressionId of the expression they are currently in (most likely a ParticipantExpression).

    #
    # launching a process :
    #
fei = engine.launch(MyProcessDefinition)
    # or
fei = engine.launch(launchItem)

puts "launched process '#{fei.workflow_instance_id}'"

    #
    # from within a participant :
    #
fei = workitem.fei
    # or
fei = workitem.last_expression_id

    #
    # getting the process instance id (wfid) from a FlowExpressionId :
    #
wfid = fei.workflow_instance_id
    # or
wfid = fei.wfid

This page looks at

What’s missing for now : pausing an engine and replacing a segment of a business process on the fly.

(Maybe this page will be split in future releases, don’t link to much to it directly).

what’s going on in there ?

There are 3 methods for questioning the engine about what’s going on. The last one is certainly the most useful.

listing processes

(listing their top/root expressions)

list_processes returns a list of FlowExpression instances. These are the root expressions of all the processes currently in the engine (thus all are ‘process-definition’ expressions).

engine.list_processes.each do |flow_expression|
    puts "- #{flow_expression.fei}"
end

will produce something like :

- (fei 0.9.12 engine/engine field:__definition Def 59 20070708-hanakegeyo process-definition 0)
- (fei 0.9.12 engine/engine field:__definition Def 59 20070708-hanamujehi process-definition 0)
- (fei 0.9.12 engine/engine field:__definition Def59b 0 20070708-hanenahebo process-definition 0)

The flow expression ids may be used when cancelling whole processes.

viewing a process stack

class SampleDef0 < ProcessDefinition
    sequence do
        participant :alice
        concurrence do
            participant :bob
            participant :charly
        end
        participant :diane
    end
end

li = OpenWFE::LaunchItem.new(SampleDef0)
li.customer_name = "Acme Ltd"

fei = engine.launch(li)

#... 

puts engine.get_process_stack fei.wfid

will output something like :

* (fei Test 0 20070709-gigosedeto 0.0.1 sequence)
   `--p--> (fei Test 0 20070709-gigosedeto 0.0 sequence)
   `--e--> (fei Test 0 20070709-gigosedeto 0 environment)
   `--c--> (fei Test 0 20070709-gigosedeto 0.0.1.0 print)
   `--c--> (fei Test 0 20070709-gigosedeto 0.0.1.1 sleep)
   `--c--> (fei Test 0 20070709-gigosedeto 0.0.1.2 print)
* (fei Test 0 20070709-gigosedeto 0.0 sequence)
   `--p--> (fei Test 0 20070709-gigosedeto 0 process-definition)
   `--e--> (fei Test 0 20070709-gigosedeto 0 environment)
   `--c--> (fei Test 0 20070709-gigosedeto 0.0.0 print)
   `--c--> (fei Test 0 20070709-gigosedeto 0.0.1 sequence)
   `--c--> (fei Test 0 20070709-gigosedeto 0.0.2 print)
* (fei Test 0 20070709-gigosedeto 0.0.1.1 sleep)
   `--p--> (fei Test 0 20070709-gigosedeto 0.0.1 sequence)
   `--e--> (fei Test 0 20070709-gigosedeto 0 environment)
   `--c--> >1s<
* (fei Test 0 20070709-gigosedeto 0 process-definition)
   `--e--> (fei Test 0 20070709-gigosedeto 0 environment)
   `--c--> (fei Test 0 20070709-gigosedeto 0.0 sequence)

process status

The engine has a Engine.process_statuses(wfid_prefix=nil) method.

puts engine.process_statuses.to_s

will yield something like :

process_id        | name              | rev     | brn | err | paused?
------------------+-------------------+---------+-----+-----+---------
20070706-geramoke | Def               | 59.1    |   1 |   1 | false
20070708-giramute | Peer Review       | 0.9.1   |   2 |   0 | false

‘brn’ : means ‘branches’, for a process without concurrence, will indicate ‘1’, else it will indicate how many concurrent branches there are in the process instance.

‘err’ : indicates how many errors affect the the process instance.

The method process_statuses accepts an optional ‘wfid_prefix’ parameter

puts engine.process_statuses("2007").to_s
    #
    # only the processes of 2007

puts engine.process_statuses("200707").to_s
    #
    # only the processes of July 2007

The method engine.process_status(wfid) will directly yield the process status for a given process instance :

puts engine.process_status("20070706-geramoke").to_s

View the ProcessStatus class rdoc.

pausing and resuming processes

Since OpenWFEru 0.9.14, it’s possible to pause and resume processes individually. The engine features a pause_process(wfid) and a resume_process(wfid) methods.

engine.pause_process('20070706-geramoke')
puts engine.process_statuses.to_s

will result in something that looks like :

process_id        | name              | rev     | brn | err | paused?
------------------+-------------------+---------+-----+-----+---------
20070706-geramoke | Def               | 59.1    |   1 |   0 | true
20070708-giramute | Peer Review       | 0.9.1   |   2 |   0 | false

Cancelling a paused process instance will cancel it altogether.

To resume a paused process instance :

engine.resume_process('20070706-geramoke')

Operations on paused processes result in errors called ‘paused errors’. The resume_process() call takes care of replaying these particular errors so that the process instance is resumed in a consistent state. Those paused errors will nevertheless appear in the process status error count.

cancelling processes

If you know the ‘wfid’ of a process instance, it is easy to cancel it :

engine.cancel_process(wfid)

This is equivalent to the cancel-process of the OpenWFEru process definition language.

Cancelling a process instance will remove all its expressions from the engine. Cancelled participant expressions will send ‘cancel items’ to the participant implementation (that should be able to handle them).

cancelling expressions

Cancelling a process is easy to understand : bang, the whole process instance is gone. Cancelling a process amounts in fact to cancelling the root expression of a process instance.

It’s possible to cancel parts of a process instance.

engine.cancel_expression(flow_expression)
    # or
engine.cancel_expression(fei)

Cancelling an expression will cancel the expression itself and all its children. thus cancelling a ‘sequence’ or a ‘concurrence’ expression will cut a whole segment away.

For cancelling expression from the process definition itself, the undo expression is worth a look.

replaying after errors

It’s sometimes necessary to detect and fix issues in a process execution.

The usual issue is the non-responding participant, if the participant is for example a web service, it may be down when the process instance required it and an error occured, blocking the flow. It possible to unblock the process by ‘replaying at the error’.

The classical replay process is described in the following paragraphs.

the (replay) process

An error resolution process could be summarized as :

  1. spot and fetch the error
  2. analyze it and fix the root cause
  3. replay (at the error)

spotting

Process execution will sometimes result in an error. As already seen ProcessStatus (via engine.process_statuses()) yields information about the errors affecting process instances.

if engine.process_status(my_wfid).errors.size > 0
    puts "problem with process instance '#{my_wfid}'"
end

    #
    # the longer version (with error display) :
    #
status = statuses.process_status(my_wfid)
if status.errors.size > 0
    puts "problem with process instance '#{my_wfid}'"

    status.errors.each do |error|
        puts
        puts error.to_s
    end
end

Error tracking is done via a service of the engine named the ‘error journal’. The method process_statuses() consults this service when called, but it’s possible to query it directly :

if engine.get_error_journal.has_errors?(my_wfid)
    puts "problem with process instance '#{my_wfid}'"

    engine.get_error_journal.get_error_log(my_wfid).each do |error|
        puts
        puts error.to_s
    end
end

fixing

The cause of the error might be internal to the engine (missing or misnamed participant, OpenWFEru bug, ...) or external (resource behind participant not available, ...).

In any case, a careful reading of the error trace is indispensable, the cause of the error is stated clearly and explicitely there 99% of the time. (and Google is your friend).

engine.get_error_journal.get_error_log(my_wfid).each do |error|
    puts
    puts " --- the error stack trace ---"
    puts
    puts error.stacktrace
    puts
end

replay

With a ProcessError instance at hand, this is possible :

engine.replay_at_error error

This method call will take care of removing the error from the journal before replaying it. Only ‘active’ errors will remain in the error journal.