Zeta Workflow 01: Workflow Communication

Execution Variables

One way to communicate with the workflow is via execution variables. You can use getVariable(), setVariable(), hasVariable(), etc. to work with the execution variables. My tests show the execution variables do get correctly persisted with the database tie-in package.

Unfortunately it’s not that simple. To explain, I need to throw out a lot of details about how executions work.


Execution End

If the workflow completes, its variables are no longer available to you. It’s that simple!

Well, no, it’s not quite that simple. Here’s how it actually works.

  • If it’s a sub workflow that ended, so far as I know there is no reliable way to access its variables after-the-fact.
  • If it’s the main workflow that ended, your workflow runner still points to that execution. getVariable() and getVariables() work as expected. We’ll see this used in various unit tests which check the ‘slide’ variable contents after the execution has ended.

Execution Pause

If the workflow execution is paused, you can use getVariable() etc. to work with the workflow variables. However getVariable() and friends only refer to the main workflow. If there are multiple execution threads active, each thread has its own set of variables.

In the case of parallel threads, I don’t know what happens with respect to execution variables. I’ve not investigated that area.

In the case of a sub workflow, the sub workflow definitely has its own set of execution variables. When you call and return from the sub workflow, you can specify which variables get passed from parent to child, and upon child end, which variables get passed from child back to parent.

If the sub workflow is paused, you can use the Wrapper’s getActiveVariable() and getActiveVariables() to tunnel down to the paused workflow and access its variables. I have only tested this where the pause is inside an Input Node.

In short, execution variables are really only good for carrying state internal to the workflow. They aren’t of much use for external communication.

User State Object

One technique which I think should work is carrying around a “user session id.” The user session id can be passed from parent to child workflow. The main workflow could begin with an input node requesting the user session id. So, start the workflow execution, and resume it with the user session id.

I think you can set execution variables (and attach plugins and listeners) after instantiating the workflow runner but before calling start(). However, I have not tested this.

This user state object (as identified by the user session id) needs all of the usual global locking mechanisms, detection of stale cache, etc.

I picture the User State Object as an eloquent model backed by the laravel database. The user session id generator can be based on a single-column table with an auto-increment field. The User State Object requires locking and unlocking, which in itself can be tricky.

Service Object

A Service Object is a callback. Here is how we build an Action Node which calls a Service Object:

    protected function buildSlideAction($name) {
        $actionParms = array('class' => 'StarTribune\Workflow\ServiceObject\SlideProvider',
                            'arguments' => array($name));
        return new \ezcWorkflowNodeAction($actionParms);
    }

The above code is in Wrapper/SlideHelper.php.

Now, what will the Action Node do when the workflow execution reaches that node? It will:

  1. Create a new SlideProvider object, passing the arguments list into its constructor.
  2. Call its execute() method, passing in its argument.

In other words, the action node does this:

$service = new SlideProvider($name);
$result = $service->execute($this);

If $result is true, workflow execution continues. If false, it suspends at this point. Here is what SlideProvider looks like:

class SlideProvider implements \ezcWorkflowServiceObject {
    private $_slide;
    public function __construct($slide) {
        $this->_slide = $slide;
    }

    public function execute(\ezcWorkflowExecution $execution) {
        $execution->setVariable('slide', $this->_slide);
        return true;
    }

Remember that when we defined this action node, we hard-coded $name, that is, the slide name. The SlideProvider is, in effect, a value object which remembers the hard-coded name and spews it out when that node of the workflow is executed.

Back to the User State Object

Let’s assume that we do successfully pass the “user session id” around to all execution threads and sub workflows. At any point, then, we could invoke a Service Object which examines and updates the User State Object. The User State Object is global to all workflow executions, so we don’t have to worry about which set of execution variables is current.

So long as we properly coordinate (lock) User State access, we’re fine. The User State might need to persist across weeks and months, as might the execution itself.

Plugins

I have not experimented with plugins. For now, let’s assume they work as advertised. If we plan to actually use workflow plugins, naturally, we should write some tests validating our use cases.

Plugins are documented here: http://ezcomponents.org/docs/api/latest/Workflow/ezcWorkflowExecutionPlugin.html. Your plugin can be called: 

  • After the execution is cancelled, ended, resumed, started, suspended. Your plugin is called with the execution having the event.
  • Before and after a node is activated, and after a node is executed. Your plugin is called with the execution having the event, and the specific node.
  • After a thread is ended, started. Your plugin is called with the execution having the event, the thread id, and for started, the parent id and number of thread siblings.
  • Before and after a variable is set or unset. You get the execution, the variable name, and for set, the value being set.

I believe the way you add a plugin is to instantiate the execution runner, then add plugins before calling start().

Plugins might become part of our error handling strategy. That will be the topic of the next post: http://otscripts.com/zeta-workflow-02-failure-handling/.