[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