原文:http://blogs.msdn.com/b/davegreen/archive/2005/10/20/483309.aspx
Windows Workflow Foundation supports three basic styles of workflow: Sequential, State Machine and Data-Driven.
I get a lot of people asking me which style is right for their problem, so I thought I’d share my thoughts on this with you all.
Let’s start with a simple problem. I want Fred to review a document, then Joe to approve it, and finally I want to send it to my customer.
This is an obvious Sequential style workflow. To implement it, I create a Sequential Workflow project, add a sequence of Activities which ask Fred to review, Joe to approve, and finally myself to send the document – and I’m done.
A Sequential workflow is characterized by the fact that the workflow is in control. Fred, Joe and I get to do what we’re told, when we’re told to do it. We do our stuff, let workflow central know that we did it, and then the workflow decides what happens next.
Of course, the Sequential style doesn’t mean that things always happen in a simple linear sequence like this. We can have conditional branching, loops, and so on. What it means is that the workflow controls the sequence. The Sequential style is the classic style of workflow, as implemented by dozens of products over the years.
In my opinion, it is also significantly responsible for giving Workflow a bad name. Not that there’s anything wrong with telling people what to do (I’m known to indulge in the practice myself, occasionally) – but sometimes it just doesn’t work.
Let’s look at an example. Say that I’m testing a product that’s being developed. When I find a problem, I open a bug, assign it to the guilty developer, then wait confidently for the fix. I want to write a workflow to manage this process.
So far, this sounds very familiar. The steps are: tester opens bug, developer fixes bug, tester approves fix. Just like the simple document review we saw before.
But this is illusory. What really happens? A tester opens a bug, and assign it to Bill. Bill says, no not me, this is Clive’s, and reassigns the bug to him. Or Bill says, this tester is not in this case quite correct (or words to that effect), and rejects the bug as nonsense. Or asks the tester for clarifying information. Or even, if he’s in a good mood, fixes it and hands it back to the tester. Or, if the original tester is out, another tester. Or the tester withdraws an erroneous bug (surely not). And so on, with each participant being able to make one of a set of choices at any given stage.
What happens if I write this in the Sequential style? Something like this (if you’ll forgive my pseudocode):
Tester T creates instance of bug workflow
T adds bug details
T assigns to developer D
LabelA: Switch
D assigns to developer E
Goto LabelA
D rejects bug to T:
Switch
T accepts rejection:
T updates bug and assigns to developer F:
Goto LabelA
End Switch
D requests info from T:
T submits info
Goto LabelA
D submits solution to T:
T withdraws bug:
End Switch
You get the idea. Loops and choices which arise within choices are posing structural questions (here I held my nose and used Goto, to try and keep the mapping from the scenario to the code obvious). And if we start making the process more realistic still, with a queue of bugs coming in that a development team leader assigns to individuals (or a developer might grab them from the queue), and we add a project manager to the picture with the ability to set bug priorities in flight, and so on, things will get worse and worse.
This problem is much better tackled using the State Machine style. The pseudo-code above becomes:
State: Initial
Action: T adds bug details
Action: T assigns to developer D; new state = Fixing
State: Fixing
Action: D assigns to developer E
Action: D rejects bug to T; new state = Rejected
Action: D requests info; new state = Pending Info
Action: D submits solution; new state = Pending Approval
Action: T withdraws bug; new state = Closed
State: Rejected
Action: T accepts rejection; new state = Closed
Action: T updates bug and assigns to developer F; new state = Fixing
State: Pending Info
Action: T submits info; new state = Fixing
State: Pending Approval
Action: T rejects solution; new state = Fixing
Action: T accepts solution; new state = Closed
State: Closed
This is much cleaner and more comprehensible. Also, adding more features will not complicate the structure – it will simply mean adding more states and actions.
Implementing this style in Windows Workflow Foundation is simply a matter of creating a State Machine Workflow project and defining the states and actions required.
So what’s the criterion for using the State Machine style? Simply this: are the important choices being made outside the workflow? Is theuser in control? If so, then the Sequential workflow’s notion that it calls all the shots will become a nuisance. The State Machine style of workflow, on the other hand, expects the choice of what to do to be made outside the workflow.
So if the workflow makes no choices, what is it for? Well, a State Machine workflow controls the sets of choices. It makes no sense for a tester to accept a solution until one has been submitted. It only becomes valid when the bug workflow has reached an appropriate state – by one of a large number of possible routes.
It’s this last point that leads us to another insight about why the State Machine style is more applicable to this problem. The Sequential workflow, of its nature, encodes all the possible sequences of behavior in its structure. But here, we don’t care. We only need to know about the current state, and what can be done next. So if we spend time modeling routes through the process, event though we don’t in fact care about them, and these routes are many, as they are in the bug problem, then the Return On Investment from the Sequential style inevitably becomes very poor.
OK, so far, so good. What’s this third, Data-Driven, style about?
This time, we’ll use the example of an inventory shortfall. An assembly line is making a gadget, and the computer said there were enough widgets in stock for the purpose, but when the stockroom manager went to fetch the widgets, there was a shortfall of 10.
We want to build a workflow to handle this scenario.
What are the possible actions? The supplies department could order more widgets, perhaps going to a different supplier or paying more money for faster delivery. The account manager could go to the customer and defer delivery, or split the delivery in two parts and bear the extra shipping cost. The production manager could take assembled gadgets from an order for another customer and divert them. The stockroom manager could search his stock to find the missing widgets.
Our workflow will be a collaboration, containing all these actions, restricted to the appropriate roles. Any given action might be performed multiple times. One obvious constraint is that the collaboration is not done until the shortfall is fixed by some combination of the above actions.
There will also be business constraints. For instance, there may be a rule that says deferral of delivery to gold customers is never permitted. Also, the actions will affect each other. For instance, we may say that total added cost from corrective action may not exceed 5% of original factory cost – so placing an order for accelerated supplies might prevent a shipment being split.
This is not a Sequential workflow – all the decisions are being made outside the workflow. Is it a State Machine workflow? Clearly, the sets of actions allowed to each role varies as the collaboration progresses – as splitting shipments becomes impossible, for instance – and the workflow is determining these sets of actions.
But the set of actions available at any given point is determined by the interaction of a number of independent rules – whether the customer is a gold customer, whether we have already deferred delivery once, whether the profit margin on the order is becoming a problem, etc. So the number of possible sets of actions – and therefore the number of corresponding states – is going to be large.
Crucially, we’re actually not interested in what these possible combinations of actions are – only that the rules are enforced. So we find ourselves again in a situation where a modeling approach, in this case the state machine, captures information we don’t care about – and therefore has poor ROI.
What do we get ROI from modeling? Why, simply what are the available actions, and who can perform them under what circumstances. This is just a set of actions, and for each, a role and a boolean expression which determines availability.
There is one more thing. We’d like to know when our collaboration is done – so we add to the model another boolean expression which is true when the collaboration is finished. In this case, the expression will test whether there are, or will be, enough widgets in stock for assembly.
How is this Data-Driven style implemented in Windows Workflow Foundation? There are two model elements to support this approach: the Constrained Activity Group, and the Policy. Both are typically used within a Sequential Workflow project, and represent regions of ‘data-drivenness’.
Clearly, it would be possible to model all workflows in this Data-Driven style. Wouldn’t we then have only one modeling approach to worry about?
This is true, but not optimal. To see why, consider how we know that a Data-Driven workflow is correct. We cannot predict its behavior very easily at all – the number of possible different series of actions that the workflow will allow is very large. So really the only way to test it is to try it, using enough different initial states, and enough different paths through it, that we feel confident in its operation.
Contrast the testing of a Sequential style of workflow. It has only a few possible sequences of behavior, which we can test exhaustively. We can get a higher level of confidence more cheaply.
So the motto is, choose the workflow model which has as much structure as your problem has – and no more. Deviating in either direction costs you money. Using a style with too much structure adds cost because you’re encoding information which has no value. Using a style with too little structure adds cost because your testing costs are higher than they need to be.
And one final word. Do not think that a typical real world application should use only one style. Most applications are most cost-effectively built from a composition of styles. Consider a Call Center application where most of the time the system uses scripts to drive the telephone operators. Probably a Sequential workflow. But then there are always the exceptions, such as an account in a shouldn’t-have-got-there state. Now we want to refer to an expert. Experts need to make choices – and so should be supported with a State Machine or Data Driven workflow.
So there you have it – my thinking on styles of workflow in the Windows Workflow Foundation. Feedback, as ever, solicited and welcome!
网上有人翻译:
Windows Workflow Foundation 支持三种基本的工作流模式:顺序工作流(Sequential), 状态机工作流(State Machine)和数据岛工作流(Data-Driven). 许多人都问我如何选择他们的问题,所以关于这点我愿意与大家分享一下我的看法。我们首先从一个简单的问题开始。有一篇文稿,我让弗雷德(Fled)来检查,让乔尼(Joe)来审核,最后由我来把它发给我的客户。
这是一个显而易见的顺序工作流。为了实现我们问题,我创建了一个顺序工作流工程,分别增加了弗雷德检查,乔尼审核,最后我来把它发送到用户并完成工作流的顺序活动(Sequence Activity)。 一个顺序工作流的特点在于“工作流总是在控制之中”的事实。当弗雷德,乔尼和我被告知要做那些我们先前定义好的事情时,我们就开始做。我们做我们该做的事情,当我们已经做完了便通知工作流,然后由工作流来决定接下来发生什么。当然顺序工作流并不意味着所有的事情都像上面我们描述的那样只在一条直线上发生,我们也同样可以有条件分支、循环等等。换句话说,就是一个顺序工作流的特点在于“工作流总是在控制之中”的事实。当弗雷德,乔尼和我被告知要做那些我们先前定义好的事情时,我们就开始做。我们做我们该做的事情,当我们已经做完了便通知工作流,然后由工作流来决定接下来发生什么。
顺序工作流模式作为一个古典的工作流模式被许多工作流产品所支持和基于已经有很多年了。依我看,给工作流一个不好的名字也是需要相当注意的。倒不是告诉人们该做什么有任何错误(有时候我也以自己的习惯行事),当有些时候这样确实是无法工作的。让我们看一个另外一个例子。比如说我正在测试一个正在处于开发阶段的产品。当我发现一个问题,于是我新建了一个bug,并把它指派相应的开发人员,然后 “安心地"等待程序员修复它。我将要写一个工作流来管理这个过程。到现在来说,一切听起来都很正常,步骤应该是:测试人员新建bug,开发人员修复 bug,测试人员关闭bug。这就像我们刚才那个关于文稿的例子。但这个方案是不牢靠的,测试的例子里真正的发生了什么呢?一个测试人员新建了一个bug,并且将它指派了比尔(Bill)。但是比尔说这不是我的问题,这应该是克莱夫(Clive)的问题,并且重新指派给了他;或者比尔说这根本就不是一个bug(或者类似说法),将其标识为不做处理;或者他要求测试人员给于更详细的信息;更甚者,他的心情很好,修复了bug并反馈给了测试人员;或者最初的测试人员有事不在,只有其他测试人员;或者测试人员撤销了这个错误的 bug(不安全地)。每一个参与者都能做出一套不同选择在给定的位置上。如果我以顺序工作流模式写这个例子会发生什么?可能会像这样(如果你能宽恕我这段伪代码):
Tester T creates instance of bug workflow T adds bug details T assigns to developer D LabelA: Switch D assigns to developer E Goto LabelA D rejects bug to T: Switch T accepts rejection: T updates bug and assigns to developer F: Goto LabelA End Switch D requests info from T: T submits info Goto LabelA D submits solution to T: T withdraws bug: End Switch
你也学看出来了,出现在选择之内的循环和选择正在造成结构性的问题。(这里我捂着自己的鼻子使用了Goto,是为了让代码和设定的情形保持映射关系。)我们可以让这个例子更现实些,当有很多的bug接踵而来的时候,团队领导会把任务指派给团队中的人(或者是采取开发人员抢占的模式),并且增加了工程快照和设置bug优先级等方式等等,事情将会越来越糟糕。
这个例子引起的问题使用状态机模式是一个更好的方式。上面的伪代码变成了这:
State: Initial Action: T adds bug details Action: T assigns to developer D; new state = Fixing State: Fixing Action: D assigns to developer E Action: D rejects bug to T; new state = Rejected Action: D requests info; new state = Pending Info Action: D submits solution; new state = Pending Approval Action: T withdraws bug; new state = Closed State: Rejected Action: T accepts rejection; new state = Closed Action: T updates bug and assigns to developer F; new state = Fixing State: Pending Info Action: T submits info; new state = Fixing State: Pending Approval Action: T rejects solution; new state = Fixing Action: T accepts solution; new state = Closed State: Closed
这样是更清晰和更易于理解的,并且增加更多的特征也不会使整个结构变得复杂——仅仅是增加更多的状态机和动作。
实现这个状态机模式在Windows Workflow Foundation是非常简单的事情:只需要创建一个状态机工作流工程,然后定义状态机和所需要的动作即可。评判使用状态机模式的标准是什么呢?简言之:是否重要的选择在工作流之外做出?(are the important choices being made outside the workflow?)是否用户在控制当中?(Is the user in control? )如果上面的答案是“是”,那么用顺序工作流模式想法去做会让任务一开始就陷入麻烦之中。从另一方面来说,那些选择在工作流之外的适合用状态机工作流模式。
如果工作流不要做任何选择,那那一种模式更适合呢?恩,一个状态机工作流控制了一整套选择,对于一个测试人员来说直到问题提交之后才接收到解决方案是没有意义的。这只有当bug工作流到达了适当的状态机活动——由大量的可能的分支,后才变得有效。
这是最后一点证明为何状态机工作流更适合这个bug程序。顺序工作流天生地,把所有可能的顺序行为编写到它的内部结构中去。但是在这,我们并不关心它。我们仅仅需要知道当前的状态,并且下一步可以做什么就够了,所以我们把时间花费到在进程中建模线路,尽管我们实际上也并不关心他们,并且那些线路是非常多的,就像在在这个bug程序一样多,所以通过顺序工作流模式能带来的好处不可避免地微乎其微了。
本文来自:我爱研发网(52RD.com) - R&D大本营
详细出处:http://www.52rd.com/Blog/Archive_Thread.asp?SID=17972