views:

140

answers:

3

I am trying to get my Windows State Machine workflow to communicate with end users. The general pattern I am trying to implement within a StateActivity is:

StateInitializationActivity: Send a message to user requesting an answer to a question (e.g. "Do you approve this document?"), together with the context for...
...EventDrivenActivity: Deal with answer sent by user
StateFinalizationActivity: Cancel message (e.g. document is withdrawn and no longer needs approval)

This all works fine if the StateActivity is a "Leaf State" (i.e. has no child states). However, it does not work if I want to use recursive composition of states. For non-leaf states, StateInitialization and StateFinalization do not run (I confirmed this behaviour by using Reflector to inspect the StateActivity source code). The EventDrivenActivity is still listening, but the end user doesn't know what's going on.

For StateInitialization, I thought that one way to work around this would be to replace it with an EventDrivenActivity and a zero-delay timer. I'm stuck with what to do about StateFinalization.

So - does anyone have any ideas about how to get a State Finalization Activity to always run, even for non-leaf states?

+1  A: 

Its unfortunate that the structure of "nested states" is one of a "parent" containing "children", the designer UI re-enforces this concept. Hence its quite natural and intuative to think the way you are thinking. Its unfortunate because its wrong.

The true relationship is one of "General" -> "Specific". Its in effect a hierachical class structure. Consider a much more familar such relationship:-

public class MySuperClass
{
    public MySuperClass(object parameter) { }
    protected void DoSomething() { }
}

public class MySubClass : MySuperClass
{
    protected void DoSomethingElse() { }
}

Here MySubClass inherits DoSomething from SuperClass. The above though is broken because the SuperClass doesn't have a default constructor. Also parameterised constructor of SuperClass is not inherited by SubClass. In fact logically a sub-class never inherits the constructors (or destructors) of the super-class. (Yes there is some magic wiring up default constructors but thats more sugar than substance).

Similarly the relationship between StateAcivities contained with another StateActivity is actually that the contained activity is a specialisation of the container. Each contained activity inherits the set of event driven activities of the container. However, each contained StateActivity is a first class discrete state in the workflow same as any other state.

The containing activity actual becomes an abstract, it can not be transitioned to and importantly there is no real concept of transition to a state "inside" another state. By extension then there is no concept of leaving such an outer state either. As a result there is no initialization or finalization of the containing StateActivity.

A quirk of the designer allows you to add a StateInitialization and StateFinalization then add StateActivities to a state. If you try it the other way round the designer won't let you because it knows the Initialization and Finalization will never be run.

I realise this doesn't actually answer your question and I'm loath to say in this case "It can't be done" but if it can it will be a little hacky.

AnthonyWJones
Interesting post Anthony, it seems like you've thought about this one a lot! Although it has some quirks, would you say that http://stateless.googlecode.com implements the model you prefer? E.g. 'Connected' state shown in http://blogs.msdn.com/nblumhardt/archive/2009/04/16/state-machines-in-domain-models.aspx?I'd really appreciate any pointers you have to other material on this topic.Nick
Nicholas Blumhardt
@Nick: Thanks for links very interesting. As a developer I would say yes Stateless seems to be a better approach to constructing a state machine from a developers point of view than the approach in Workflow. However Workflow is targeting a different audience, whilst is attempts to keep developers on board its really aimed at the technically savvy domain expert. That said Workflow UI does a poor job of communicating the nature of contained states and even for the more high level business tasks that Workflow is aimed at the idea of truely nested machine would have nice.
AnthonyWJones
Thanks for your responses. I think that WF does have some concept of transitions into and out of the parent ‘superstate’ since these are logged as ‘Executing’ and ‘Closed’ activity events in the tracking service. 'Stateless' has exactly the behaviour I am looking for and I thought the phone call example was very instructive. It puzzles me why WF state machines have been limited in this respect.
David Jones
A: 

OK, so here’s what I decided to do in the end. I created a custom tracking service which looks for activity events corresponding to entering or leaving the states which are involved in communication with end users. This service enters decisions for the user into a database when the state is entered and removes them when the state is left. The user can query the database to see what decisions the workflow is waiting on. The workflow listens for user responses using a ReceiveActivity in an EventDrivenActivity. This also works for decisions in parent ‘superstates’. This might not be exactly what a "Tracking Service" is meant to be for, but it seems to work

David Jones
A: 

I've thought of another way of solving the problem. Originally, I had in mind that for communications I would use the WCF-integrated SendActivity and ReceiveActivity provided in WF 3.5.

However, in the end I came to the conclusion that it's easier to ignore these activities and implement your own IEventActivity with a local service. IEventActivity.Subscribe can be used to indicate to users that there is a question for them to answer and IEventActivity.Unsubscribe can be used to cancel the question. This means that separate activities in the State's inialization and finalization blocks are not required. The message routing is done manually using workflow queues and the user's response is added to the queue with appropriate name. I used Guid's for the queue names, and these are passed to the user during the IEventActivity.Subscribe call.

I used the 'File System Watcher' example in MSDN to work out how to do this. I also found this article very insructive: http://www.infoq.com/articles/lublinksy-workqueue-mgr

David Jones