本文继续剖析基于Notes/Domino的文档工作流系统的设计和代码,以方便用户能利用和创建自己的工作流。(CSDN的下载资源1旦上传就不能修改,很不方便,现已将下载地址改到GitHub,若发现下载有问题,请与我联系。)
在前文对工作流建模时,我们抽象出以下几类对象:
工作流:每一个工作流实例都寄存工作流名称、当前节点等信息,并负责处理流程各节点的操作。
采取工作流的业务对象:在这里也就是采购单。
工作流的配置对象:不同工作流实例和各个节点的操作的实际差异都是从配置数据中读取的。
最后1类对象体现为工作流、节点和操作3种配置文档,在上文已介绍。前两类对象在我们的Notes工作流里保存于同1个文档,即流程文档主文档合1。现在我们就来看看与这个文档对应的表单设计和代码类。
流程文档的字段都包括在FlowControls子表单内,主文档对应的则是主表单,这样只要将该子表单嵌入主表单,主文档就具有了运行工作流的数据。
利用它创建你的工作流时需注意以下几点:
1. 能进行工作流的操作的用户有3类,1是流程在当前节点的处理人,保存在FlowControls子表单的PersonInCharge字段里。2是能进行像取消Cancel这样的操作的特殊用户,保存在FlowHandlers字段里。3是特权用户角色,像本演示流程用的[IT]角色,能进行上述两类操作。这个[IT]不宜配置,所以在Actions按钮的隐藏公式和代码里都直接用的。所以须将它换成你的系统里代表特权用户的角色。
2. 流程名称保存在FlowName字段里。由于位于通用的子表单内,没法直接写入特定的流程名称,所以在创建主文档时,要将利用的流程名称写入该字段。例如:
Dim doc As NotesDocument
'CreateDoc is a function in LS library lsNotes
Set doc=CreateDoc("fmPurchasing")
doc.FlowName="Purchasing"
Dim ws As New NotesUIWorkspace
Call ws.EditDocument(True, doc)
3.当前状态保存在Status字段里。在表单上要显示当前状态的地方可以创建1个计算文本或显示时计算字段援用Status字段的值。
4.ActionOptions和Action用于用户点击流程操作时容纳操作选项和用户的选择。PersonInCharge和FlowHandlers分别保存流程确当前处理人和能进行特殊操作的用户。FlowEnds保证流程是不是结束的标志,初始值为0,结束时为1。CreatedBy记录文档的创建者。FlowReaders保存文档的读者,FlowWriters保存除流程处理人以外的其他可能的编辑者。FlowComments用于临时容纳用户进行流程操作时写的意见。
5.当用户点击子表单上的流程操作Actions…时,系统需要从流程操作配置文档读取数据并计算出该用户可见的操作选项。完成此功能的代码类FlowActions保存在lsSimpleFlow脚本库中。
本流程系统在客户端和XPages环境下运行使用的语言分别是LotusScript和Java。下面先讨论LotusScript下的实现。处理流程各节点操作的通用代码组成了3个类,都在lsSimpleFlow脚本库里。之所以用3个类,是为了在普通利用以外满足测试的需要。作为核心的后端代码都在AbstractFlow类中。1般流程操作都使用它的子类SimpleFlow,作用是从NotesUIDocument获得主文档,处理字段校验等触及界面交互的任务。BatchFlow类在构造函数内传入主文档,没使用到任何前端类,可用于测试或批量审批。为了方便无妨将这些类称为流程引擎。
如前文所述,这些类能完成以下流程的通用功能:
实际工作流常常会有特殊的需求,这就要求能以某种方式扩大流程类,基本上可概括为在流程提交前后履行1定的逻辑,无妨分别称为QuerySubmit和PostSubmit的业务逻辑。在Lotus Notes中理论上有很多种方式可斟酌。
1. 将特定流程的代码写在配置文档里。
由于LotusScript是1种解释型的脚本语言,流程引擎可以像读取其他配置数据1样以文本的情势读取这些代码,再解释履行。
缺点是这些代码不在Designer内编写,没有语法校验、色彩标记、格式化等帮助,运行时若出错很难调试,和没有经过预处理速度也会稍慢。
2. 在流程类的代码中添加运行特定流程逻辑的函数。
例如在流程类的Submit方法中调用QuerySubmit和PostSubmit函数,它们或与流程引擎同位于1脚本库lsSimpleFlow,或在lsSimpleFlow援用的另外一脚本库中。
缺点是通用的流程类对特定的流程逻辑产生了依赖。lsSimpleFlow脚本库没法单独部署到某个利用程序里。如果QuerySubmit之类的函数保存在lsSimpleFlow脚本库里,1旦流程引擎的代码需要更正或升级,就不能简单刷新该脚本库。如果这些特定的逻辑存于lsSimpleFlow脚本库援用的另外一个脚本库内,则该库的名称须写死,并且逻辑上让通用代码援用特定代码的设计奇怪不容易理解。
3. 采取事件机制在流程引擎中调用特定流程的代码。
要实现流程引擎与特定流程代码之间的分离,在许多程序语言中都会采取所谓事件的机制。LotusScript也支持事件,但仅限于NotesUIDocument等对象预定义的事件。为了让自定义类能够支持事件机制,我们需要自己编写代码。在33. 面向对象的LotusScript(6)之为自定义对象摹拟事件和Java、LotusScript和JavaScript中的自定义事件编程等几篇文章里我讨论了在LotusScript实现事件机制的两种途径。以二者中较优的类似Java中自定义事件的实现方式为例,要在流程引擎的Submit方法前摹拟QuerySubmit事件,需要编写1个包括QuerySubmit方法的FlowEventHandler类,然后调用流程引擎的AddEventHandler方法传入该类的1个实例,最后在Submit方法中调用该实例的QuerySubmit方法。
4. 在通用流程类的子类中写入特定流程的代码
在AbstractFlow类的Submit方法中调用空的QuerySubmit和PostSubmit函数。在SimpleFlow的子类内,如我们演示的采购工作流的PurchaseFlow,再此两函数中写入实际的代码。这类似于Java中的实现接口。
与采取事件机制相比,此方法也要求为某个特定的流程写1个类。另外一个好处是有需要时在子类中可以覆盖通用流程类的其他方法,例如流程操作配置文档难以满足某1操作选择下1节点的复杂要求时,可以覆盖GetNextNode方法,在其中写入所需的逻辑。
5. 在1个援用流程引擎的脚本库中以函数而不是类的方式编写特定流程的代码
上述两种方式中的类代码都是在某个援用lsSimpleFlow库的脚本库里编写的,那末是不是1定要以面向对象的情势呢,直接以1组函数的方式编写特定流程的代码,在主函数中顺次序调用通用流程类和其它函数如何?
与方法4相比,此途径只有几个细微的劣处:
由于PostSubmit的逻辑从在SimpleFlow类的Submit方法内调用变成从主函数中调用Submit方法后运行,本来在Submit方法内完成的前端文档NotesUIDocument的1系列Reload、Refresh、AutoRefresh操作也宜移至PostSubmit函数内。再加上QuerySubmit和PostSubmit函数里必定会用到主文档等其他对象,必须写代码取得,而不能如在子类中那样直接援用。
通常QuerySubmit的逻辑是在Submit方法内的表单校验通过后再运行,以函数的方法顺序调用QuerySubmit和SimpleFlow对象时就没法做到这1点。
综合以上讨论,我们选择方法4――在通用流程类的子类中写入特定流程的代码。特定流程类的代码容纳于lsLocalFlow脚本库中,此脚本库名不能不固定下来,由于在调用它的FlowControls子表单需要肯定地援用它的名称。子表单的Actions操作终究调用的是这个脚本库里的SubmitFlow函数:
Function SubmitFlow(flow As String, curNode As String, action As String)
If Not IsDebuggingLS() Then
On Error Goto ErrorHandler
End If
'Dim objFlow As New SimpleFlow(flow,curNode,action)
Dim objFlow As New PurchaseFlow(flow, curNode, action)
Call objFlow.Submit
Exit Function
ErrorHandler:
Call objFlow.RollBack()
MessageBox Error & " occured when flow submitted at line " & CStr(Erl),64,"Error " & CStr(Err)
Exit Function
End Function
从上面的代码可见,如果当前流程没有特殊的业务逻辑,可使用通用的流程类SimpleFlow,否则便要用1个写好的SimpleFlow的子类,即此处的PurchaseFlow。最后谈谈这里被包括在If语句中的毛病处理。
上面的代码如果利用普通的毛病处理,普通用户看到的毛病信息会略微友好1些。但由于骨干代码都包括在Submit方法里,捕捉到的毛病产生的行几近注定就是这1行,而这对肯定真正出错的代码帮助不大。并且由于毛病被捕捉了,打开Notes客户真个调试LotusScript工具也没用。如此看来,仿佛不应当添加毛病处理代码。但此处有1个奥妙的问题。流程类的Submit方法履行时如果产生毛病,很有可能已修改了当前主文档,乃至已修改了文档的状态和存取控制字段的值,在这样的数据“破损”状态下,用户关闭文档时如果保存,则将产生1个没法解释其数据和进行后续操作的异常文档,例如流程已处于下1节点,但文档权限并未改变。通用的流程引擎类尚可以精心编写反复测试,尽可能减少出错的可能。但是特定于某流程的代码,一样可能造成上述问题,它们的质量却取决于各自的作者。
在使用其他编程语言和关系型数据库时,保证数据不会处于此种不1致状态的解决方法是事务(transaction)。1个事务内的1系列状态改变,或成功,或失败时回滚至原位。LotusScript和Notes数据库不支持此功能。我们能做到的只是尽可能摹拟,所以AbstractFlow类中有1个RollBack方法,可以将主文档的所有域值恢复到保存在1个临时备份文档中的初始状态。SubmitFlow函数里添加毛病处理代码的目的就是利用RollBack方法避免毛病产生时主文档的数据处于不肯定状态。剩下的问题便是出错时如何调试,最好有1个便捷的途径在不修改代码的情况下使毛病处理代码失效。这就是IsDebuggingLS和1个工具代理ToolsToggle LS Error Handler的由来。它们通过设置和检查1个特殊的Notes环境变量“DEBUG_LS”来控制毛病处理代码是不是生效。后者将环境变量“DEBUG_LS”的值在0和1之间切换,前者检查该值是不是为1。这样在SubmitFlow函数里,如果检查到处于“调试”状态,就不进行毛病处理,此时便能用调试器跟踪毛病。
上一篇 【机器学习基础】验证