最近正在忙一个应用集成系统的设计,所以有好长时间没有写工作流相关的文章了。不过这期间我们一直没有放弃对WF的探索。功夫不负有心人,我们在工作流的应用上取得了实质性的进展。因为商业目的,这里不能公布源代码。但为了大家的共同提高,我还是要把一些技术上的关键点给以说明。这期间我和同事做了大量研究,在此对他们表示感谢。
让我们很快切入正题。引入工作流的意义大家都很清楚:将不断变化的处理流程独立出来,以降低开发成本、维护成本,或是其它的东西。那么能达到一种什么样的效果,对于我们这些初次使用的人们来讲更多的是良好的期盼,因为对于WF的应用确实不是一件很容易做的事情。在此之间的一系列文章也只是对一些技术点的探索。现在终于可以整合一下了。
这里拿我们的一个实际需要进行阐述。电台、电视台里的稿件审核流程是一件非常严肃的事情,每个台都有自己一套自己的审核流程,即使一个分台可能视稿件的重要程度、紧急程度、稿件内容的不同有不同的审核流程。举例如下:
l 常规审核流程:一级一级的自下往上审核。如编辑审à科组长审à分台审à总台审。
l 终审制:是常规审核流程的一种扩展。即每个环节都有最终审核权。只要该环节认为没有必要让上级(领导)进行审核,则可以进行此操作。
l 跨审:存在这样的情况,流程中的某一环节因为某种原因(可能相关人员请假了)而导致流程无法运行下去。有两种解决方法:一种是通过延时活动在等待一定时间后,自动将活动跳转到下一个处理环节中去,这种方式的缺点是必需等待;另一种操作方式是,下一个要处理环节的相关人员可以直接审核上一个环节所涉及的稿件内容,这种审核方式即跨审是一种更为及时的操作方式。
每一个环节的审核又可能有下面的表决方式:
l 权重值性要求:环节中参与的每一个用户都有一个权重值。当同意权重值之和达到一定期望值后,则该环节通过。或者反对权重值之和达到一定的期望值则认为是未通过审核。
l 角色性要求。如:某一环节要求五个编辑中的三个如果同意则进入下一个环节,如果有两个表示反对则打回。另一个例子:只要有两个副台或一个正台同意则通过,只要有一个副台或一个正台否决则打回。
l 人员性要求。这个相对简单,只要指定可参与的人员列表,及通过人数和否决人数即可。
如何有效地让客户自已去灵活地、随时随地地设计使用这些流程,而不需要工程人员或售后人员的干预是一个非常棘手的问题。客户关心的是流程中有几个环节。如果让用户自己去用IFELSE或WHILE去设计每一个环节的详细内容,则客户很快会否决这套系统,即使是技术熟练的工程人员也会皱眉的,维护成本会大幅的攀升的,而且开发人员还要为工作流设计器中的每一个条件做一些复杂和额外的工作。
理想的情况是这样的。用户所面对的工作流设计器非常简单的:他们可以从工具箱中插入一个活动到到工作流编辑界面中去,每一活动所代表的是一个完整的处理环节,而不是处理环节中的一部分;这就是简化用户设计的关键所在。用户不需要为设计一个具体环节的逻辑而感动头痛,他们所做的只是设置一下与业务相关的活动的属性就可以了。这样设计出来的工作流看起来会非常的直观、简练。
要让用户有这样的用户体验,我们得需要进行大量的工作。其主要内容简列如下。
l 工作流编辑器
l 自定义的审核活动
l 基于工作流的工件维护框架
l 工作流设计管理框架
l 分布式部署的要求
然而要将这些有机的组合在一起,使之在不同的应用系统中进行应用,这就是业务工作流平台的概念了。待续。
的确正如别人所说的那样,WF只是一个foundation而不是一个platform,如果要实现完整的业务工作流平台支持,就这个平台的实现来讲,从技术上说不是很难,我想微软应该可以实现这一点,也许是下一个版本:)。或许就platform和foundation来讲,微软更看中的是foundation。总之现在的应用还是比较麻烦的事情。
那么在微软没有platform的时候,我们怎样实现这个platform呢?我们先看一个我们遇到的问题:
l 每种业务领域有自己不同的工作流程,这一点比较好解决,我们可以为不同的业务领域放置不同的模板。
l 在同一业务领域中,不同的应用可能会有很大的不同,这一点也可以通过扩展某一业务领域中的模板来进行。
l 如何方便地将应用系统中的数据集成到业务工作流平台中进行处理?这里的数据集成分两个阶段:设计时和运行时。如审核中的角色、用户等信息是在设计时指定的;对所有人员的评审数据进行存储和计算合并是在运行时进行的。所有这些数据的访问都应当由自定义活动来进行(一个相对独立的操作环节)。具体的细节在自定义活动中进行说明。
作为一个平台,上面的内容还是不够的,我会按前面的文章中所说的每一个环节进行说明。待续。
工作流设计器
一开始接触WF时,我就想WF中的设计器是否可以直接给用户来使用,如果可以的话,那么我们便可以省去了单独开发这种图形操作界面所花的时间了。通过摸索,其中的困难还是挺多的。为此我这前也写了些相关的文章,有关如何在宿主中加载工作流设计器的话题已经在前面的文章中有说明,这里就不再述了。
这里的要说明的重点是如何设计一个通用的最终用户可自行设计的编辑器,也就是说这个编辑器不只是应用于前篇文章所说的新闻系统。这里的技术点在于,每个应用系统都有自己特定的自定义活动,而这个设计器又如何去识别这些自定义活动。对于这个技术点来讲其实现还是比较简单的,那就是用“反射”。
来看一下我们的应用系统会有什么样的要求?
l 由客户自已来设计业务工作流,操作简单、直观,
l 本身可以独立运行,也可作为二次开发的类库。
l 可以是一个独立的窗口,也可以是一个应用于其它窗口的控件。
l 如果是一个独立的编辑器窗口,我们可以设置窗口的标题。
l 可以定制根活动的标题,及相应的提示信息(非常有必要,SequentialWorkflowActivity是英文的:<)。对于如何改变活动的外观请参照前面我写的文章。
l 根活动的外观定制是可选的如,前景、背景、过渡、边框等。对于如何改变活动的外观就参照前面我写的文章。
l 我们设计时使用的活动列表,这些活动我们可以通过反射技术加以呈现。
l 将设计好的工作流导出。有两种方式一个直接生成文件,二是生成流。流可以方便应用系统自己去管理这些设计好的工作流。
l 应用系统可以给出一个设计好的工作流以进行编辑修改。
l 可以方便地修改工作流活动的各种属性,包括自定义活动的自定义属性。
对于这些定制,我们设计了一个配制文件来解决。配制文件示例如下:
<?xml version="1.0" encoding="utf-8"?>
<WWFDesigner xmlns="Infomedia.WWFDesigner">
<Appearance>
<WinformTitle>工作流设计器</WinformTitle>
</Appearance>
<Rules></Rules>
<RootActivity>
<AssemblyName>WWFDesigner</AssemblyName>
<TypeFullName>Infomedia.WWFDesigner.DefaultSequentialWorkflowActivity</TypeFullName>
<HeaderText>工作流设计器</HeaderText>
<HelpText>可以在此放活动</HelpText>
<ForeColor>Black</ForeColor>
<BackColor>White</BackColor>
<BorderColor>White,White</BorderColor>
<LineColor>Black</LineColor>
</RootActivity>
<Activities>
<Activity>
<AssemblyName>System.Workflow.Activities</AssemblyName>
<TypeFullName>System.Workflow.Activities.CodeActivity</TypeFullName>
</Activity>
<Activity>
<AssemblyName>System.Workflow.Activities</AssemblyName>
<TypeFullName>System.Workflow.Activities.DelayActivity</TypeFullName>
</Activity>
<Activity>
<AssemblyName>System.Workflow.Activities</AssemblyName>
<TypeFullName>System.Workflow.Activities.IfElseActivity</TypeFullName>
</Activity>
<Activity>
<AssemblyName>System.Workflow.Activities</AssemblyName>
<TypeFullName>System.Workflow.Activities.WhileActivity</TypeFullName>
</Activity>
</Activities>
</WWFDesigner>
这里需要注意的是<Rules></Rules>节,这里我没有给出配制的具体内容,原因是我还没这方面的实现。该节的目的是加载Rules文件,以将设计过程中的条件进行罗列,这样可以方便用户去选择条件表达式。至于条件Rules的生成,我计划是设计一个独立的表达式生成工具(让用户手动去编写这些条件表达式会被骂的^o^)。
这个条件表达式设计器也不是一件很容易的事情。表达式的组成主要有四部分组成:变量、关系运算符(=,!=,),《等)、值、逻辑运算符。其中变量和值是由具体的应用系统来决定的,这就是麻烦所在。还是由配制文件来完成这件事吧,它包含下面的内容:
应用系统中工作流活动所用到的变量列表。
每个变量的类型和取值范围。
条件设计器的产生物是一个XML化的Rules文件,即可将其指定到上面配制文件中的<Rules></Rules>节里。关于条件设计器的实现我这边还没有,如果有谁做了这方面的工作不妨分享一下。
大家可以不必太关心这个条件表达式设计器,至少我这边的项目还没有用到。因为我想我把所有的条件都用到了自定义活动中去了;也就是说工作流设计中不会有象IFELSE\WHILE之类的活动的。
说到自定义活动,请看我下面的文章,待续
自定义活动(一)
这一小节要讲的东西,目前我认为是最重要的东西。这是业务工作流平台中非常重要的一部分;;也是模板的重要组成部分。我们将从以下几个方面讲述自定义活动:
<!--[if !supportLists]-->l <!--[endif]-->尽可能减少占有时间
<!--[if !supportLists]-->l <!--[endif]-->数据交换
<!--[if !supportLists]-->l <!--[endif]-->锁定处理
<!--[if !supportLists]-->l <!--[endif]-->保持状态的一致性及完整性
<!--[if !supportLists]-->l <!--[endif]-->中止活动的运行
以下是针对自定义审核活动特有的设计
<!--[if !supportLists]-->l <!--[endif]-->功能逻辑的设计
<!--[if !supportLists]-->l <!--[endif]-->回退与重新递交
<!--[if !supportLists]-->l <!--[endif]-->跨审的实现
尽可能减少占有时间
在WWF的一些技术细节与应用体会(一)中我讲过持久化的相关描述,该节虽然不讲技术化的问题,但却与锁定时间有关。所以不得不提SqlWorkflowPersistenceService,它的构造函数里面有一个参数instanceOwnershipDuration用来指定占有的时间。占有的时间过长对于时效性处理是很有害处的。所以我们应当尽量减少instanceOwnershipDuration的值。
在现实生活时每个人的处理速度是不一样的,有的处理的快,有的处理的慢。有的人在处理过程中会去打点水,上个厕所什么的……那么我们究竟要将instanceOwnershipDuration设置为多少才合适?可能很多人会比较难以选择。
其时,我这里是在故意误导大家!一开始的时候我也这么想过。那么有什么方法可以设置到一个比较小的范围(如10s)就可以让所有的处理都可以满足要求。这里有一个现象我得说一下。一个处理过程中计算机所要做的工作有两方面的内容:一是收集用户输入的信息;二是提交数据并进行相应的自动化处理。对于第一点来讲,占用的时间是不可准确估计的,但对于第二点来讲,占用的时间是极其有限的(一般情况下10s足够了)。我想大家可能知道我在说什么了。那就是我们在工作流运行期间不负责处理用户信息的收集。
那么如何做到这一点呢?请看下面的小节。待续
自定义活动(二)
数据交换
要想使自定义活动能够通用,那么活动所需要的数据来源也必须遵循一定的规范才可以,然而这很难做到。拿自定义审核活动来讲,它可能需要在三个方面的数据(实际还要多):
l 用户设计工作流时指定用于审核的人员列表,指定哪些人可参与这个审核活动。这里有一个技术点,就是如何实现自定义属性编辑器,这里不多讲。
l 工作流实例运行时获得当前用户信息,根据当前用户自定义审核活动可以判断此人是否可以执行此活动,即此人是否在用户列表中存在。如果不可以则中断活动的运行。有关如何中断活动的运行请看后面的文章。
l 活动执行时所需要的数据。业务工作流平台设计(四)中的“尽可能减少占有时间 ”中讲到将用户数据的收集从工作流中分离出来,以节省占有时间,同时也应将数据同工作流进行分离。工作流不负责这些数据的存储和查询,但自定义审核活动会应用这些数据进行处理以决定该活动是否运行到下一步去;或是继续等待其它用户的审核;或是退回(可经修改再次提交审核);或是不被采用(不能再次提交审核)。
这些数据的获取途径是不确定的,拿用户列表数据来讲,可能是直接访问数据库,也可能是访问Web Service,或是通过配置文件……另外我们是否要求用户采用统一的存储格式。所有这些都是不确定的。那么如何与应用系统进行集成?还是自定义一个接口让应用系统去实现吧。
我们有必要进行这样的隔离,否则这个活动的通用性就会大打折扣。但如果每个活动都有自己不同的数据需要,那么这种情况也是比较繁琐的,最好的方法是集中进行配置。对于每一个设计我们都有一个相应的配置文件,把这些内容放到那里面去我想会是不错的选择。
待续。
自定义活动(三)
锁定处理
在前面的文章我已经讲过,把用户数据的收集同工作流实例中分离出来。数据的存储也就很可能地进行了分离。这种数据的分离会要求我们去做更多的工作:
l 维护工件(如稿件,任务等要在工作流中进行加工的东西)与工作流实例的关系。我们用WF自带的SqlWorkflowPersistenceService来完成工作流实例的存储。那么工件、工件与WF实例的关系及相关的处理情况应交给应用系统来维护。有关这方面的内容请看后面的“工作流的工件维护框架”。
l 保持工件与WF实例的状态一致性。这部分的解决请后后面的有关“状态一致性”的话题。
l 独占式处理。这是本节的重点内容。
独占式处理,其实处理过程很简单,就是在处理时对数据加把锁,就象进厕所把门锁上一样:)。这里不讲怎样去加锁,而是讲对哪些数据进行加锁以及何时进行加锁。
我们来看一下要完成一个审核活动所要处理的数据
l 对工件进行编辑并进行保存
l 记录工件的审核情况
l 运行完后保存WF实例的状态
其实我们需要锁定的数据只有两个:工件和WF实例。如果工件锁定不成功的话则应退出WF实例的运行,有关如何中止活动的运行,就看后续的文章;如果加锁成功则直到WF实例持久化后再解锁。注意,虽然WF的SqlWorkflowPersistenceService本身有加锁的功能,但还是要等到WF实例持久化后再对工件解锁。
这里有一件事情还要说明。工件在审核过程中可能不会被修改,如果从打开工件时刻开始加锁,则会大大的增加占有时间!切记。我们可以在工件第一次被修改的事件中进行加锁处理,以尽可能地减少占有时间。
讲到这里,细心的人可能发现,工件可能在处理过程中没有被修改,即没有产生加锁信息,此时如果我们运行了WF实例,情况是危险的。我们在进行WF时一定要检测工件是否被锁定了,如果没有锁定则进行加锁,然后再运行WF实例。
先讲到这里,可能有些人嫌我写的太少了,我只能说对不起了,我还有很多的工作要做。待续。
自定义活动(四)
保持状态的一致性及完整性
上一节讲过,要完成一个审核活动所要处理的数据有下面的内容
l工件进行编辑并进行保存
l记录工件的审核情况
l运行完后保存WF实例的状态
通过上面我们可以看到在工作流活动运行之外我们还进行了一些数据的操作,就自定义审核活动来讲,在WWF的一些技术细节与应用体会(二)中我提到过活动执行的完整性及一些实现方法,在这里是用不上的,因为我们的部分数据在工作流之外进行了处理。我们必须在工作流运行环境之外提供一个将数据的收集、工件的编辑及活动运行后WF实例的持久化放在同一事务中。
另在开发自定义审核活动时我们需要指定PersistOnClose属性。否则这个状态将不会被记录。这个属性非常关键!我们只希望在活动运行完成时自动进行持久化以便使工作流过渡到下一个活动中去。但将条件不满足时,放弃运行(不是中止!)。这对数据的状态一致性也非常重要。
待续……