Design Considerations when implementing WF

Design Considerations when implementing WF

Old forum URL: forums.lhotka.net/forums/t/3022.aspx


ctkach posted on Monday, June 11, 2007

So, I'm struggling with trying to figure out the best way to layer on Windows Workflow with the CSLA.  But first, let me put everything into context and provide some background on the application.

We have a typical business application where certain (but not all) business objects have a status.  This status can be changed only be people in certain roles.  As an example, an assistant brand manager is allowed to work with the business object (entering information and such) but is not allowed to change it's status.  The brand manager is allowed to change the status from planned to brand approved and the brand planner is allowed to move the status from brand approved to sales approved.  And each one of these status changes has it's own set of business rules that need to be checked before the status change can be completed.

So, this all sounds like the perfect place to implement a State Machine Workflow.  However, what I'm really struggling with is best approach to do this within the CSLA.

1. Do I invoke the workflow on the client-side of the data portal and use custom activities that are CSLA aware and can call through the data portal to retrieve business objects or perform other actions needed for the given status?

2. Do I use a data portal command to communicate back to the server that the status has changed and let it invoke the workflow on the server side?

I'm completely new to WF and any best practices that others have used in this situation would be appreciated.  The other thing I'm really struggling with is do I create a generic ChangeStatus(string newStatus) method that will provide the flexibility of introducing new statuses in the future or should I take the approach that I've commonly seen in all the examples where each change in state has its own method (BrandApproved(), SalesApproved()).  What sort of problems do you forsee with going with the ChangeStatus() approach?

I appreciate any insight people can give to this.

- Chris

 

RockfordLhotka replied on Monday, June 11, 2007

You need to decide, firmly and clearly, where you want to put your business logic.

Business objects are state machines all by themselves. So it is quite easy to envision using one or more objects to implement what you describe. It is not possible to separate the concept of a state machine from a stateful object - they are state machines.

Of course a state machine workflow is also a state machine, and can't be anything else.

So what you have is a situation where you want one state machine to be "empty" - maintaining the state but not controlling it, and the other state machine doing the work.

Let's presuppose that your workflow is the one you want doing the work. This directly implies that you need your object "state machine" to be passive, so it can allow the workflow to set the state as required. In that case, the object may just have a SetState() method that sets the state to any supplied value. This way the workflow can arbitrarily set the state and save the object.

In fact, you may find that a Command object stereotype is more appropriate (implemented with either CommandBase or ReadOnlyBase as appropriate - search the forum for my discussions of command stereotypes). This way each workflow activity can simply execute a command object's static/Shared SetState() method to alter the state.

THEN there's the question of how do you invoke the workflow itself. Presumably the user did something to trigger a possible change of state. They probably edited some other object that, as a side-effect, may (or may not - the rules are in the workflow remember) change the state. I would suggest that in THAT OTHER OBJECT'S DataPortal_XYZ method, you invoke a command object that invokes the workflow.

In other words, the workflow should be encapsulated within your domain object model - probably behind a command object. That way your other objects can, as needed, invoke that command to invoke the workflow.

The workflow itself, as it runs, will use objects to retrieve and update the status value. The activities can load the data (via objects), run the rules as needed and (if appropriate) execute a command object to alter the status value.

If the new status value is important (must be returned to the calling business object), then the command object that executed the workflow can get the new status, either from the workflow itself (via dependency properties) or from the database. This value can be returned as a result of the shared/Static SetState() method that ran the command.

lprada replied on Tuesday, June 12, 2007

It will be great to have a few examples of this like the DeepData projet.

RockfordLhotka replied on Tuesday, June 12, 2007

Examples would be nice, but can easily become a full-time job - one I don't want Smile [:)]

However, I do write various things as I speak. For example, I did a whole series of implementations of the same app for Tech Ed - click here to download.

These samples include a couple workflow examples - one that directly calls a business object from within an activity, and one that calls a WCF service, which in turn calls the business object.

EaglePower replied on Sunday, June 17, 2007

Hi Rocky!--

Excellent information. I work for Navy and we are using CSLA and want to replace another WorkFlow system with .NET WF. Can you elaborate on what changes would need to be made to an application data schema to accomodate WF and how that ties to the Persistence Schema that comes with WF or if a custom Persistence schema is in order, and how that Persistence Schema ties to the data model from which the CSLA business objects are instantiated?

My struggle is determining how many and what and where data model changes need to be typically made in order to accomodate adding on a state-machine WF to an existing CSLA application.  I know the stock persistence schema with WF keeps the GUID for the WF instance that has not yet completed.  I am guessing I need to associate that GUID as a field on my record from which my business object is created?  For example, maybe someting like a  "WFInstanceId" field added to my record for let's say a Publication record being moved through some state-machine WF where it must be acted upon by diffent users with queues (lists) that get populated by querying for Publication business objects of the state that signifies that they are in their queue.

Do I also need a "State" field on the business object and its record in the database, or will that reference to the WF instance GUID suffice in order to return a STATE to the business object that can be used to filter it for population into various appropriate queues or GUI listviews for user actions depending on their roles?

Thanks!  This seems to be the info that is missing from all the books I have read and all articles I have read so far.

J.T. Taylor

RockfordLhotka replied on Sunday, June 17, 2007

I'm not a WF expert, so I can't give definitive answers, sorry.

In general terms, the WF persistance model persists the dependency property values of the workflow by serializing them (using the BinaryFormatter as I understand it). Due to this, you should not expose CSLA business objects as dependency properties (as tempting as that is).

While the objects will serialize and deserialize just fine, there's a versioning issue. If you upgrade your business DLL while the workflow is suspended, the workflow won't be able to deserialize, and thus won't be able to resume.

This isn't a CSLA problem - it is a WF problem with any complex type. Due to this, you have to be quite careful to only expose native types (int, double, string, etc) or be vulnerable to this versioning issue.

(disclaimer: I'm not a WF expert, so perhaps they have a magic solution to this - but I doubt it, because it is one of those unsolvable problems...)

So from an architecture perspective, what this means is that each activity can use objects. Those objects can interact with your real database, changing data as appropriate for the activity.

At the workflow level you can keep status values and other peripheral data in simple form.

If you have a long-running workflow, it is up to you to manage the Guid value that identifies the workflow instance. Typically if you suspend a workflow it is because the user is being asked to provide input or take some action. You can (must?) associate that Guid with the user request, so it can round-trip back to the code you write to resume the workflow instance once the user is done doing whatever they did.

I doubt there's any one-size-fits-all answer for how you do this. It very much depends on what you are doing, and how you are doing it. Perhaps you are merely sending an email to a user, and they are emailing back a response. In that case, you might need the Guid to be in the subject or something. In other cases you may have a to-do table in your database, and each entry in that table is an item assigned to a user, along with the Guid for the workflow.

Copyright (c) Marimer LLC