[Zope3-dev] A possible component architecture interpretation of
workflow
Jim Fulton
jim at zope.com
Wed Sep 8 14:57:52 EDT 2004
Here is a longish collection of thoughts on workflow that I've been
working on for a while. I'd like to get some feedback on them, before I
pursue prototypes and, eventually a proposal.
A while ago, I was introduced to the Plone/CMF Form Controller.
CMF Form Controller provides a way to separate form display,
validation, and navigation. Rules are specified for what happens when
a form is submitted. You can specify which validators run depending
on:
- The type of object being displayed, and
- The submit button used
Further, you can specify what page to display (or redirect to) next
based on:
- The success or failure of the validators,
- The type of object being displayed, and
- The submit button used
I think this capability has a lot of value. We've seen a need for a
better separation of display, logic, and navigation in Zope 3 view
implementations. I've been pondering how to fold this sort of thing
into Zope 3's presentation architecture.
To me, this feels very similar to workflow. In workflow, you have a
collection of activities tied together by transitions. Rules
determine when transitions fire and the processing associated with
them. A form controller expresses the rules for performing processing
on pages and the transitions between them. This observation made me
start to wonder if perhaps workflow might have a far more central role
in Zope 3 user-interface design than I had previously considered.
I should preface what follows by noting that I am *not* an expert on
workflow. I've dealt with workflow in Zope from somewhat of a distance
and I'm approaching workflow now from a distinctly
component-architecture point of view. This adds both a fresh
perspective and a possible bias in viewing workflow.
The Workflow Management Coalition (WFMC) defined a workflow reference
model that defines a workflow process as a collection of activities
tied together via transitions. An activity represents an
interaction between the system and an external entity, typically a
user. (An external entity could also be some eternal application.)
Transitions between activities are triggered by events, which are
usually user events (e.g. a user selecting a button on a form). A
workflow process has associated "workflow-relevant" data that is used
to control transitions between activities. Work lists are used to
associate activities with the users or organizational roles that are
supposed to perform them. Activities may be hierarchical and
concurrent. For example, an an activity can be split into
sub-activities that can execute concurrently. Some transitions in the
containing activity are conditioned on completion of sub-activities.
So how does this fit in to Zope 3? First, I want something that is as
simple as possible. Second, I want to leverage the
component architecture as much as practical, introducing as few new
concepts as possible. The component architecture has events. The
component architecture has subscribers, which can be used to express
the rules for making transitions between activities and for performing
other processing in response to events. Objects and interfaces can be
used to represent processes and activities.
I propose to use the terms "process" and "activity" for the
corresponding concepts in the WFMC reference model. I'd like to treat
these as patterns, rather than as object classes. In Zope 3, a
process is simply an object with behavior that varies over time.
Activities represent different behaviors the object takes over time.
(Technically, the activities are *really* the time-varying user
interactions provided for an object.) Activities can take two forms:
- States
A process may have states. States are expressed as interfaces.
When a process (object) transitions between states, it changes the
interfaces declared for it.
Using interfaces to represent states, state-dependent behavior is
provided by components registered against the state interfaces. To
access state-dependent behavior, it's necessary to look up
components that provide the behavior.
An alternative to changing an object's interface when it changes
states is to change the object's class or to change the object
itself. (The later becomes necessary if persistence is involved,
because persistent objects are currently not allowed to change their
class.) This has the advantage that state-dependent behavior is
more intrinsic to the object and doesn't rely solely on component
lookup. It has the disadvantage that it is more complicated and
especially complicates object identity and references between
objects if the object (rather than just the object class) changes.
- Subprocesses
A subprocess is used when an activity is split into concurrent
subactivities. Subprocesses execute independently of one another and of
the containing activity. Subprocesses are implemented as subobjects.
To see how this might work, consider a process for preparing a
technical report of some sort. At some point, we need the report to be
reviewed by technical reviewers. Each technical reviewer needs to
follow a process for completing their review. The reviewers work
independently of the each other. Some work on the report can proceed
while the technical reviewers are doing their work. We implement
the technical reviews with separate review objects created for each
technical review. Each review object is a process, with it's own
activities.
Both states and subprocesses support hierarchical decomposition of
activities. States can be decomposed into substates using interface
inheritance::
class IState11(IState1):
"IState11 is a substate of IState1"
An object that is in State11 is also in IState1. Hierarchical
decomposition of subprocesses is obvious.
This model encompasses both the DCWorkFlow and OpenFlow workflow
approaches. (Hopefully, my relative ignorance of OpenFlow won't get
me in trouble here. Appologies to the OpenFlow authors if I got this
wrong.) A DCWorkflow content object is a process whose activities are
states. An OpenFlow process is simply a process object, whose
activities are states or subprocesses. The biggest change with
respect to the OpenFlow model is that processes and activities are
represented by application objects rather than by dedicated workflow
objects.
Examples
Let's look at 3 examples:
- A shopping-cart implementation, which demonstrates a fairly
straightforward workflow application,
- A report-management system, in which workflows processes split into
concurrent activities, and
- An edit form, which wouldn't normally be addressed with workflow.
Purchasing work flow
Consider an e-commerce site. Among other things, the site allows
people to shop and to purchase items collected into shopping carts
while shopping. We can represent this as two states, shopping and
checking out.
Users in a shopping site have shopping carts. These shopping carts
are stored as session data. The shopping carts can be empty, or
non-empty. While shopping, people can add items to their shopping
carts. We have a shopping state with 2 substates, no items
selected and items selected. A user can transition to the
check-out state only from the shopping items-selected state.
In the check out state, a user has to enter various order data
until a minimal set of data has been gathered. This can be
represented as two substates of check out, incomplete and
complete. From either of these check-out states, a user can
return to shopping. If they return to shopping, they will still
have unpurchased items in their shopping cart. (They may lose
sensitive order information, depending on security/privacy
policies.) From the complete state, they can submit their order.
In this case, the order is submitted to a fulfillment system,
their cart is emptied, and they return to shopping.
In this workflow implementation, The process object is stored in
the session. It may be the shopping cart itself, or it might
contain the shopping cart. We use interfaces on the cart to
represent the states. We have substates in this case, which we
can represent with interfaces. The check out state might actually
be a schema, which gives the object extra information in that
state.
Research Report
Preparing a research report may require multiple preparation and
review steps. In addition, preparing a research report may
involve the collaboration of many people and the creation of many
artifacts, such as drafts and reviews concurrently.
Someone is assigned to write a report. This starts a
report-preparation process. The process includes a first draft
report object. The authors of the report work on the first
draft. At some point, they submit the draft for editorial
review. At this point, they can't work on the report without
creating a new draft. The editorial reviewer could approve the
draft for publication, or they can require editorial changes. In
any case, they create an editorial review that contains any
comments or decisions they make. They can also require some
number of technical reviewers. A separate review object is
created for each review, including the editorial review and each
technical review. Reviews can be carried out in parallel.
Authors can work on the next draft in parallel with the reviews.
The authors are required to respond to each review, either
agreeing to make all changes or saying specifically what changes
they won't make or will make differently from what was suggested
by either technical or editorial reviewers. The same process is
repeated for subsequent drafts until the editor has decided that
the report is ready for publication. A draft cannot be submitted
until each of the reviews for the previous draft have been
completed *and* responded to.
We can model this workflow process as a tree of state-dependent
objects. Below, bullets indicate subobjects and the word "state"
indicated states withing an object. The work "parallel"
indicated subprocesses that execute in parallel. Other
subprocesses execute sequentially.
- Report-preparation process
State: preparation
- Repeating draft-preparation sub-processes
State: in progress
- Parallel: Reviews
State: in progress
- Parallel: Zero or more review objects for previous draft
State: preparation
State: response
State: completed
State: completed
- Parallel: Draft
State: preparation
State: completed
State: completed
State: published
State: Rejected
Here are some additional points to note about the process:
- Reviews of a draft proceed in parallel with the preparation of
the next draft. Obviously, there are no reviews during
preparation of the first draft and there typically won't be
work on a next draft during review of the final draft.
- Within a draft-preparation subprocess, additional reviews can be
added if the (next) draft is not completed.
- Within a draft-preparation subprocess, a draft can be completed,
only when all of the reviews have been completed. When a draft
is completed, a new draft-preparation process is created with an
editorial review.
- Within a draft-preparation subprocess, a technical review is
normally completed after the reviewers have prepared and submitted
their review and the authors have prepared and submitted their
response. An editorial reviewer can short-circuit the review
process by moving the review to the complete state at any time.
- At any time, an editorial reviewer can end the process by causing
a transition to either the published or rejected states. If they
transition to the publish state, they select a completed draft to
be published. This need not be the most recent draft.
Edit forms
Note that we'll automate common cases. Edit forms are a good
examples. We now have a fairly simple ZCML directive for setting
up an edit form based on a schema. We'll still have this, but the
form it generates will actually be a workflow process with two
states, unchanged and changed. The process instance's data will be
"stored" as request-form data.
Decisions about how and when to process the form and on what to
display when a form is submitted will be expressed as event
subscribers. There will be ZCML facilities for wiring these
subscribers up to form events.
See the section on user events below.
Possible Mechanisms
So far, I've described a mostly conceptual model. I've touched on
some of the mechanisms that enable this model, namely objects to
represent processes and subprocesses and interfaces to represent
states. Let's look at some other needed mechanisms.
User events
We need some way to convert user actions to events. Why? Because
we want to decouple the logic for responding to user events from
the logic for displaying user interfaces. This is the "controller"
part of form controller and of the original
"model-view-controller" pattern.
We Will compute user events by adapting a published object and a
request to IUserEvent. If we can find an adapter, we'll then
publish the event in the usual manner.
I'm unsure exactly when this should happen, or what code should be
responsible for it. It might be done by workflow aware views, or
it might be done during the publication process, perhaps in
response to a publication event. I don't think we'll know the
best approach here without some prototyping.
The most common sort of user event is someone submitting a form.
Perhaps browser view directives (view, page, pages, xxxform, ...)
will grow button directives::
<page
name="foo.html"
...
/>
<button name="update"
event=".FooUpdate"
subscriber=".myupdatehandler"
/>
<on event="zope.app.form.browser.Updated"
redirect_to="index.html" />
...
</page>
Where the event values are names of event factories called
with the view and the request. Subscribers to the resulting
event are then notified.
I've also shown a directive that might be used to control what to
do next. I'm waving my hands here. :)
Something to note is that the subscribers defined by the 'button'
and 'on' directives are registered for the specific view class
defined by the page directive, so they apply just to the view
defined.
State Transitions
There are a number of issues with state transitions:
- How are they triggered
- How are they affected
- How do state transitions affect process/sub-process
interactions.
Again, it's best to try the simplest thing that works first.
Transitions are typically triggered by events. There is nothing
magic about this. Python code in event subscribers calls a
transition API. IOW. any Python code can cause a state transition
by calling an API.
We affect a transition by adapting an object to IProcess.
IProcess defines a transition method that is called with an old
state and a new state. The implementation of IProcess is
responsible for doing at least the following, in order:
- Generate an IBeforeTransitionEvent, which has, as attributes,
the object undergoing the transition, and the new state interface
- Computing an object in the new state. This may be the old
object with different interfaces. This object is returned from
the transition method.
- Generate an IAfterTransitionEvent, which has, as attributes,
the object undergoing the transition, the old state, and the
object after the transition. This may be the same as the object
undergoing the transition.
It may be that most systems have a single adapter that doesn't
really depend on the object.
Work lists
Work lists are simply containers where we can "assign" processes
to people or groups. Work lists are not an integral pert of the
workflow model. Rather, they are a feature of a particular
workflow application. For example, work lists don't make any
sense for edit forms or shopping carts. They make a lot of sense
for report preparation.
Independent states
An object may actually represent more than one process at a time.
For example, a review might be in the review process, with 3
states and in the locking process, with 2 states. It has
state-dependent behaviors from each of the process models that
apply to it.
Generic states
We might want to model states generically. So, for example, rather
than having "draft locked" and "review locked", we might have just
"locked". This means we want to register views for multiple
interfaces. For example, we might want to register a view for
locked and draft. We can accomplish this with multi-adapters.
Let's say we want to find an edit view, 'edit.html'. We register
an edit view factory for IUnlocked. This view then dispatches to a
multi-view:
def UnlockedEdit(context, request):
return zapi.getMultiView((context, context), request,
name='edit.html')
We should automate this with some ZCML changes. Maybe something
like:
<browser:page
name="edit.html"
for=".IReport"
state=".IUnlocked"
...
Process definition
A system might provide facilities for managing process definitions
as content. For example, there might be a system for importing
process definitions in standard formats. In any case, there is
likely to be value in expressing and reviewing process definitions
as a unit.
Issues
- WFMC compatibility.
The WFMC reference model provides a conceptual model *not* an
implementation blue print. It defines systems, such as a
"Workflow Enactment Services" or "Workflow Engines" not to
specify specific implementation components, but to provide units
of functionality that a workflow implementation can be expected
to provide. I think that, in Zope 3, it is important to keep
this distinction in mind, so that we can arrive at a workflow
model that is well integrated with th rest of the Zope 3
architecture. At a lower level, the WFMC specifies specific
objects, such as processes, activities and process
instances. These are a bit more concrete because the WFMC
provides interchange APIs and formats to allow
process-definitions to be exchanged between systems and to support
other forms of system inter-operation. While we may need to
support these models at system boundaries to support interchange,
I think it would be a mistake to constrain the internal
implementation to follow these models to closely.
Thoughts? :)
Jim
--
Jim Fulton mailto:jim at zope.com Python Powered!
CTO (540) 361-1714 http://www.python.org
Zope Corporation http://www.zope.com http://www.zope.org
More information about the Zope3-dev
mailing list