NAME Method::Workflow - An OO general purpose declarative workflow framework DESCRIPTION This module provides an Object Oriented workflow framework. By default this framework uses keywords for a declarative interface. Most elements of the workflow are defined as methods, and will ultimately be run on a specified object. This Framowerk is intended to be used through higher level tools such as Fennec. As such the API leans twords providing more choices and capabilities. In most use cases an API wrapper that hides most of the descisions should be implemented. SYNOPSYS This synopsys makes no attempt to convey a use case. This is simply an example of how to use the API. You define nestable workflows which should define scenarios and data. You also define tasks which make use of that data. use strict; use warnings; use Method::Workflow; my @color; my $wf = start_workflow; workflow rainbow { $self->do_thing; # $self is automatically given to you, it will be the # $invocant object listed below workflow red { $self->do_thing_again; #$self is free again. task red { push @color => 'red' } } workflow yellow { ... task yellow { push @color => 'yellow' } } workflow green { ... task green { push @color => 'green' } } workflow blue { ... task blue { push @color => 'blue' } } } # Define on object that the methods will be run on. my $invocant = SomeClass->new() || undef; # What return data do we care about? my @want = qw/ results task_results /; # Do it. my ( $results, $task_results ) = $wf->run_workflow( $invocant, @want ); WORKFLOWS AND TASKS Workflows will run to depth in the order they are defined. Tasks are run *after* all workflows complete. This leads to the potential for powerful advanced workflows. It also leads to potential spooky action. workflow root { my $thing; workflow a { $thing = 'a'; task { print "$thing\n" } } workflow b { $thing = 'b'; task { print "$thing\n" } } } This example will print b twice, that is because all workflows run first, meaning the value of $thing is set to 'b' before the tasks run. This is not a bug, but rather a desired feature. See Method::Workflow::SPEC and Method::Workflow::Case for expamples. UNDER THE HOOD Method::Workflow::Util exports several methods for manipulating 'the stack'. This is not the perl stack, but rather a stack of workflows. The stack is an array of workflows. When you use the declaritive keywords such as 'workflow NAME { ... }' a new workflow is created, this workflow is added as a child to the topmost workflow on the stack. When a workflow is run, it is pushed anto the stack as the topmost item, thus any methods declared within are added to the proper parent. When you call run_workflow() on a workflow it will run the workflows method, then recurse into nested workflows. Once workflows have been run to depth the tasks and errors are propogated down to the initial run_workflow() call. At this point errors are handled, and tasks are run. API To create a workflow without magic: my $workflow = Methad::Workflow->new( name => $name, method => sub { # When magic is used $invocant is actually shifted off as $self. # $workflow is this workflow. my ( $invocant, $workflow ) = @_; ... }); The invocant is reffered to as $self for the sake of higher level libraries which will hide the workflow details from their users. EXPORTS $root_wf = start_workflow() Starts a workflow and pushes it on to the stack. It is important to store the workflow as it will be shifted off of the stack when the reference count hits zero. It is also important to either remove all references, or call $root_wf->end when you are done defining the workflow. workflow NAME { ... } Define an element of a workflow task NAME { ... } Define a task for the current workflow ORDERING TASKS There are 3 sorting options, 'ordered' (default), 'sorted', and 'random'. They can be specified when defining a workflow, or when calling start_workflow. here is an example from the tests: my @order; $wf = start_workflow( sorted => 1 ); workflow root { task c { push @order => 'c' } task a { push @order => 'a' } task b { push @order => 'b' } task 'y' { push @order => 'y' } task z { push @order => 'z' } workflow x ( ordered => 1 ) { task f { push @order => 'f' } task e { push @order => 'e' } task d { push @order => 'd' } } } $wf->run_workflow(); is_deeply( \@order, [ qw/ a b c f e d y z /], "Nested re-ordering" ); ABSTRACT METHODS These are convenience methods that allow you to hook into the workflow process. $obj = $obj->init( %args ) Called by the constructor giving you the opportunity to change or even replace the created object at construction time. @list = $class->required() Should return a list of attributes that are required at construction. @list = $obj->pre_run() Should return a list of coderefs that should be called after this workflows method is run, but before any nested workflows are run. @list = $obj->post_run() Should return a list of coderefs that should be called after nested workflows are run. $obj->run() Should run this workflows method, and store results and errors. Here is the default example. sub run { my $self = shift; my ( $invocant ) = @_; $self->push_results( $self->do( $self->method, $invocant )); } SIMPLE ACCESSORS $wf->parallel( $max ) $max = $wf->parallel() Get/Set the max number of parallel processes to use when running tasks in parallel, 0 means do not run tasks in parallel. $wf->method( \&code ) $code = $wf->method() Get/Set the method referenced by the workflow. $wf->name( $name ) $name = $wf->name() Get/Set the workflow name. $wf->debug( $bool ) $bool = $wf->debug() Turn debuging on/off. $ordering_type = $wf->has_ordering() Return the stringified name of the ordering method, undef if none is specified. $bool = $wf->ordered() $wf->ordered( $bool ) True if tasks are to be run in the order in which they were defined. $bool = $wf->random() $wf->random( $bool ) True if tasks are to be run in random order. $bool = $wf->sorted() $wf->sorted( $bool ) True if tasks should be sorted ACTION METHODS $wf->observe() Mark the workflow as observed (happens when run) $bool = $wf->observed() Check if the workflow has been observed $wf->add_item( @items ) $wf->add_items( @items ) Add tasks/workflows to this one. (called when keywords are used) $wf->begin() Set this workflow as the current on the stack. $wf->end() Pop this workflow off the stack (errors if this workflow is not the top) @return = $wf->do( $code, $invocant ) Run $code as a method on $invocant. The return is what $code returns. @results = $wf->run_workflow( $invocant, @want ) Run the workflow. @results is an array of arrays, each inner array is the list of returns for an element of @want. Possible @want values are: results, errors, tasks, task_results, task_errors, task_tasks. MANIPULATING RESULTS These methods manipulate the results array which stores the return value from the method with which the workflow was created. The run() method is responsible for populating this. @items = $wf->results() Get the results @items = $wf->pull_results() Get the results while also deleting them from the workflow. $items = $wf->results_ref() Get/Set the arrayref storing the results. $wf->push_results( @items ) Add results MANIPULATING TASKS @items = $wf->tasks() Get the tasks @items = $wf->pull_tasks() Get the tasks while also deleting them from the workflow. $items = $wf->tasks_ref() Get/Set the arrayref storing the tasks. $wf->push_tasks( @items ) Add tasks MANIPULATING ERRORS @items = $wf->errors() Get the errors @items = $wf->pull_errors() Get the errors while also deleting them from the workflow. $items = $wf->errors_ref() Get/Set the arrayref storing the errors $wf->push_errors( @items ) Add errors MANIPULATING NESTED WORKFLOWS @items = $wf->children() Get a list of all nested workflows (not to depth) @items = $wf->pull_all_children() Get all the nested workflows while also deleting them from the workflow. @items = $wf->pull_children( $type ) Get all the nested workflows of a specific type while also deleting them from the workflow. $items = $wf->children_ref() Get/Set the hashref storing the nested workflows. $wf->push_children( @items ) Add a nested workflow my @types = $wf->keys_children() Get a list of all the types of nested workflows (What they are blessed as) CUSTOM ERROR HANDLERS $wf->error_handler( \&custom_handler ) $handler = $wf->error_handler() Get/Set the error handler. The error handler should be a coderef. All errors will be passed in as aguments. Each error is an array, the first element is a workflow stack trace, the second is the error message itself. The stack trace is an array of workflow objects. Here is the default handler as an example: sub default_error_handler { for my $set ( @_ ) { my ( $trace, $msg ) = @$set; warn join( "\n ", $msg, 'Workflow Stack:', map { blessed($_) . '(' . $_->name . ')' } @$trace ) . "\n"; } die "There were errors (see above)"; } EXTENDING To extend Method::Workflow you should subclass Method::Workflow, possibly subclass Method::Workflow::Task, and familiarize yourself with Method::Workflow::Stack. Also read the section 'UNDER THE HOOD'. FENNEC PROJECT This module is part of the Fennec project. See Fennec for more details. Fennec is a project to develop an extendable and powerful testing framework. Together the tools that make up the Fennec framework provide a potent testing environment. The tools provided by Fennec are also useful on their own. Sometimes a tool created for Fennec is useful outside the greator framework. Such tools are turned into their own projects. This is one such project. Fennec - The core framework The primary Fennec project that ties them all together. AUTHORS Chad Granum exodist7@gmail.com COPYRIGHT Copyright (C) 2010 Chad Granum Method-Workflow is free software; Standard perl licence. Method-Workflow is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the license for more details.