请假流程描述
流程图:
假设:公司有两级领导,一级为主管(Chief),一级为老板(Boss),我们这里只是一个模拟,当然现实生活中情况比这个更加复杂;-)
描述:
- 在某公司中,部门员工休假需要主管(Chief)的批准。
- 如果休假天数大于10天,则 在部门主管同意后,还必须老板(Boss)批准。
- 如果是部门主管请假则直接提交老板批准。
- 在休假被批准之前,申请人可以撤销休假申请。
- 申请批准后,对休假天数进行修改(也可以是其他业务数据处理)。 每次休假申请结束之后,不管通过未通过或是否取消,都必须记录下来。
- 流程结束时,系统要把请假的结果信息Email给申请人。
- 对于大于10天的申请,如果部门主管已批准同意而上级主管还未批准,这时申请人撤销申请后,系统应发Email通知部门主管申请已撤销。
流程定义之processdefinition.xml
processdefinition.xml
1
<?xml version="1.0"?>
2
<!-- NOTE:在定义流程时,建议先画出流程图,然后再来定义,这样思维清晰,也不易于出错
3
关于processdefiniton.xml如何定义,请严格按照nPdl规定 -->
4
<process-definition>
5
6
<!-- =================================== -->
7
<!-- == PROCESS DEFINITION PROPERTIES == -->
8
<!-- =================================== -->
9
<name>请假DEMO</name>
10
<description>该流程模拟公司的请假流程, Made By LuBen</description>
11
<responsible>ae</responsible>
12
13
<!-- ====================== -->
14
<!-- == START & ENDSTATE == -->
15
<!-- ====================== -->
16
<start-state name="start leave request">
17
<description>提交请假单</description>
18
<!-- 定义了role,引擎在该start-state节点执行时,就会把执行者信息赋值给角色对应的属性“requester” -->
19
<role>requester</role>
20
<!-- 在这里定义start-state的field,它表示该filed相关联的属性,并且在该state,它对属性的访问权利。
21
如果需要定义其在web表单上的操作界面,如何进行web表单显示等,需要在webinterface.xml文件对应节点补充该field -->
22
<field attribute="start date" access="write-only-required" />
23
<field attribute="end date" access="write-only-required" />
24
<field attribute="leave days" access="write-only-required" />
25
<field attribute="comment" access="write-only" />
26
<transition to="Is Cancel Fork" />
27
</start-state>
28
29
<!-- 结束节点除名称外不要定义其他-->
30
<end-state name="end" />
31
32
33
<!-- ====================== -->
34
<!-- == Actions == -->
35
<!-- ====================== -->
36
<!-- 解释:这里定义process-definition节点的action,有效的事件类型为:process-instance-start, process-instance-end and process-instance-cancel -->
37
38
<!-- 此处具体为:在流程结束的时候, 发送E-Mail消息给申请者,记录请假日志 -->
39
<action event="process-instance-end"
40
handler="NetBpm.Example.LeaveOfAbsence.EmailAction, NetBpm.Example.LeaveOfAbsence" on-exception="log">
41
<!--定义action参数,供委托类实例化类调用方法时获取使用。如这里的EmailAction的run方法发送邮件,需要知道发给谁,邮件标题等等,那么
42
参数可以提供辅助-->
43
<parameter name="to">previousActor</parameter>
44
<parameter name="subject">您提交了请假申请</parameter>
45
<parameter name="message">you requested a holiday from ${start date} to ${end date} with comment ${comment}</parameter>
46
</action>
47
<!-- 此处具体为:在流程结束的时候记录请假日志, 此处Log模拟 注意:每个节点可以定义多个action -->
48
<action event="process-instance-end"
49
handler="NetBpm.Example.LeaveOfAbsence.LogLeaveInfoAction, NetBpm.Example.LeaveOfAbsence" on-exception="log">
50
<parameter name="LogInfo">记录请假日志? :) </parameter>
51
</action>
52
53
<!-- ================ -->
54
<!-- == ATTRIBUTES == -->
55
<!-- ================ -->
56
<!-- 解释:定义属性值及其序列化方式。属性值一般包括3类 -->
57
<!-- one:角色对应的属性 -->
58
<attribute name="requester" type="actor" />
59
<attribute name="chief" type="actor" />
60
<attribute name="boss" type="actor" />
61
62
<!-- two:所有acitivity-state(包括start-state)处需要更新的属性,和用户表单内容对应 -->
63
<attribute name="start date" type="date" />
64
<attribute name="end date" type="date" />
65
<attribute name="leave days" type="integer" />
66
<attribute name="comment" type="text" initial-value="请假理由或者备注" />
67
<attribute name="Chief evaluation result" type="evaluation" />
68
<attribute name="Boss evaluation result" type="evaluation" />
69
70
<!-- three:标识属性,该属性不会在web界面上体现,主来用来存储流程标识信息,供后面的逻辑处理使用 -->
71
<!-- 该属性不会在界面上呈现, 在具体流程路径中被设置以决定Decide Which Action如何走 -->
72
<attribute name="RunTrace" type="text" />
73
<!-- 该属性不会在界面上呈现, 该属性被用来设置流程结束将要发送给用户的邮件信息(邮件标题, 如果需要邮件内容可扩展) -->
74
<attribute name="ToUserEmailSubject" type="text" />
75
76
<!-- =========== -->
77
<!-- == NODES == -->
78
<!-- =========== -->
79
<!-- 定义流程包含的所有节点 -->
80
81
<!-- 定义decision节点, 从decision可以流出多条边(transition),这些边可以目的地相同,也可以不同。
82
如下面,虽然decision分出了3条供选择的边,但是边的目的地都是end节点,只是边的名称不同 -->
83
<decision name="Decide Which Action"
84
handler="NetBpm.Example.LeaveOfAbsence.WhichWayDecision, NetBpm.Example.LeaveOfAbsence">
85
<parameter name="attribute">RunTrace</parameter>
86
<transition name="approve" to="end">
87
<action event="transition" handler="NetBpm.Example.LeaveOfAbsence.AutoSetAttributesAction, NetBpm.Example.LeaveOfAbsence" on-exception="log">
88
<parameter name="attribute">ToUserEmailSubject</parameter>
89
<parameter name="setValue">您的请假申请已经被批准 holiday from ${start date} to ${end date} with comment ${comment}</parameter>
90
</action>
91
</transition>
92
93
<transition name="disapprove" to="end" >
94
<action event="transition" handler="NetBpm.Example.LeaveOfAbsence.AutoSetAttributesAction, NetBpm.Example.LeaveOfAbsence" on-exception="log">
95
<parameter name="attribute">ToUserEmailSubject</parameter>
96
<parameter name="setValue">您的请假申请没有获得批准, holiday from ${start date} to ${end date} with comment ${comment}</parameter>
97
</action>
98
</transition>
99
100
<transition name="requestercancel" to="end">
101
<action event="transition" handler="NetBpm.Example.LeaveOfAbsence.AutoSetAttributesAction, NetBpm.Example.LeaveOfAbsence" on-exception="log">
102
<parameter name="attribute">ToUserEmailSubject</parameter>
103
<parameter name="setValue">您取消了请假申请, holiday from ${start date} to ${end date} with comment ${comment}</parameter>
104
</action>
105
<!-- 请假人取消请假的时, 如果主管已经审批了, 则需要发邮件通知他 -->
106
<action event="transition" handler="NetBpm.Example.LeaveOfAbsence.EmailChiefAction, NetBpm.Example.LeaveOfAbsence" on-exception="log">
107
<!--- action的参数只能有3个, 切记切记, 调试的时候搞晕了-->
108
<parameter name="to">role(chief)</parameter>
109
<!--<parameter name="cancelMan">previousActor</parameter> -->
110
<parameter name="subject">请假申请被取消啦</parameter>
111
<parameter name="message"> requested a holiday from ${start date} to ${end date} with comment ${comment}</parameter>
112
</action>
113
</transition>
114
</decision>
115
116
117
<!-- ====================== -->
118
<!-- == CONCURRENT BLOCK == -->
119
<!-- ====================== -->
120
<!-- 解释:定义并发块,concurrent-block一定包括一个fork,一个join,且边不可越出其界。concurrent-block是可以嵌套的 -->
121
<concurrent-block>
122
<!-- 定义fork,若没有定义forkhandler,那么其在fork flow时将按照默认行为执行 -->
123
<fork name="Is Cancel Fork">
124
<transition name="CancelLeaveRequest" to="Request Cancel" />
125
<transition name="evaluation" to="IsChiefDecision" />
126
</fork>
127
128
<!-- 定义join,此处定义了joinhandler,则当有forked flow到达join时,将根据该委托判断是否要激活父flow,
129
若没有定义joinhandler,那么将按照默认行为(AndJoin委托类,也就是所有forked flow都汇聚到达Join才激活父flow)执行 -->
130
<join name="join before decided" handler="NetBpm.Example.LeaveOfAbsence.AnyOneJoin, NetBpm.Example.LeaveOfAbsence">
131
<transition to="Decide Which Action" />
132
</join>
133
134
<activity-state name="Request Cancel">
135
<description>在请假申请被最终审批前, 您可以在这里取消申请.</description>
136
<role>requester</role>
137
<!-- fields are optional. they limit the access to attributes in an activity -->
138
<field attribute="start date" access="read-only" />
139
<field attribute="end date" access="read-only" />
140
141
<!--eg。请假理由备注先不被看到
142
<field attribute="leave days" access="read-only" />
143
<field attribute="comment" access="read-only" />
144
-->
145
<action event="perform-of-activity" handler="NetBpm.Example.LeaveOfAbsence.AutoSetAttributesAction, NetBpm.Example.LeaveOfAbsence" on-exception="log">
146
<parameter name="attribute">RunTrace</parameter>
147
<parameter name="setValue">requestercancel</parameter>
148
</action>
149
<transition to="join before decided" />
150
</activity-state>
151
152
<decision name="IsChiefDecision" handler="NetBpm.Example.LeaveOfAbsence.IsInRoleDecision, NetBpm.Example.LeaveOfAbsence">
153
<parameter name="checkrole">chief</parameter>
154
<transition name="isChief" to="BossApprove" />
155
<transition name="isNotChief" to="ChiefApprove" />
156
</decision>
157
158
<activity-state name="ChiefApprove">
159
<description>您需要在这里对该请假单进行审批</description>
160
<assignment handler="NetBpm.Example.LeaveOfAbsence.AssignmentExpressionResolver, NetBpm.Example.LeaveOfAbsence">
161
<parameter name="expression" >role(requester)->group(hierarchy)->role(chief)</parameter>
162
</assignment>
163
<role>chief</role>
164
<field attribute="requester" access="read-only" />
165
<field attribute="start date" access="read-only" />
166
<field attribute="end date" access="read-only" />
167
<field attribute="Chief evaluation result" access="write-only" />
168
<action event="perform-of-activity" handler="NetBpm.Example.LeaveOfAbsence.EvluationResultAction, NetBpm.Example.LeaveOfAbsence" on-exception="log">
169
<parameter name="attribute">RunTrace</parameter>
170
<parameter name="setValueAttribute">Chief evaluation result</parameter>
171
</action>
172
<transition to="IsMoreTenDays" />
173
</activity-state>
174
175
<activity-state name="BossApprove">
176
<description>您需要在这里对该请假单进行审批</description>
177
<assignment handler="NetBpm.Example.LeaveOfAbsence.AssignmentExpressionResolver, NetBpm.Example.LeaveOfAbsence">
178
<parameter name="expression" >role(requester)->group(hierarchy)->role(boss)</parameter>
179
</assignment>
180
<role>boss</role>
181
<field attribute="requester" access="read-only" />
182
<field attribute="chief" access="read-only" />
183
<field attribute="start date" access="read-only" />
184
<field attribute="end date" access="read-only" />
185
<!-- 理论上Boss不需要看到主管是否同意,因为不同意就不会到他这里来
186
<field attribute="leave days" access="read-only" />
187
<field attribute="Chief evaluation result" access="read-only" />
188
-->
189
<field attribute="Boss evaluation result" access="write-only" />
190
<action event="perform-of-activity" handler="NetBpm.Example.LeaveOfAbsence.EvluationResultAction, NetBpm.Example.LeaveOfAbsence" on-exception="log">
191
<parameter name="attribute">RunTrace</parameter>
192
<parameter name="setValueAttribute">Boss evaluation result</parameter>
193
</action>
194
<transition to="join before decided" />
195
</activity-state>
196
197
<decision name="IsMoreTenDays" handler="NetBpm.Example.LeaveOfAbsence.IsMoreOrLessDecision, NetBpm.Example.LeaveOfAbsence">
198
<parameter name="attribute">leave days</parameter>
199
<parameter name="dividingDays">10</parameter>
200
<!--考虑扩展,形成对数值比较类的通用 <parameter name="datatype">float</parameter>
201
<parameter name="compareType">More</parameter> -->
202
<transition name="IsMore" to="BossApprove" />
203
<transition name="IsnotMore" to="join before decided" />
204
</decision>
205
206
</concurrent-block>
207
208
</process-definition>
209


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

91

92

93

94

95

96

97

98

99

100

101

102

103

104

105

106

107

108

109

110

111

112

113

114

115

116

117

118

119

120

121

122

123

124

125

126

127

128

129

130

131

132

133

134

135

136

137

138

139

140

141

142

143

144

145

146

147

148

149

150

151

152

153

154

155

156

157

158

159

160

161

162

163

164

165

166

167

168

169

170

171

172

173

174

175

176

177

178

179

180

181

182

183

184

185

186

187

188

189

190

191

192

193

194

195

196

197

198

199

200

201

202

203

204

205

206

207

208

209

流程定义之webinterface.xml
webinterface.xml
1
<?xml version="1.0"?>
2
3
<!-- 关于webinterface.xml如何定义,请严格按照nPdl规定
4
只有需要人操作的节点, 也就是activity-state(包括StartState), 在该文件中才在需要进行定义,指定如何显示web表单等 -->
5
<web-interface>
6
7
<!-- ==================== -->
8
<!-- == PROCESS IMAGE == -->
9
<!-- ==================== -->
10
<!-- 定义该流程的流程图片 -->
11
<image name="web/leavedemo.gif" mime-type="image/gif" width="510" height="560" />
12
13
<!-- ================ -->
14
<!-- == ACTIVITIES == -->
15
<!-- ================ -->
16
<activity-state name="start leave request">
17
<!--定义该节点在流程图中的坐标-->
18
<image-coordinates>
19
<x1>285</x1>
20
<y1>54</y1>
21
<x2>307</x2>
22
<y2>76</y2>
23
</image-coordinates>
24
<!--逐个定义该节点fields的web显示方式,htmlformatter是委托类,可以充分利用其委托类的特性-->
25
<field attribute="start date">
26
<name>开始时间</name>
27
<description>在此处填写您想要在何时开始请假</description>
28
<htmlformatter class="NetBpm.Workflow.Delegation.Impl.Htmlformatter.DateInput, NetBpm" >
29
<parameter name="dateFormat">dd/MM/yyyy</parameter>
30
</htmlformatter>
31
</field>
32
<field attribute="end date">
33
<name>结束时间</name>
34
<description>在此处填写您想要在何时结束请假</description>
35
<htmlformatter class="NetBpm.Workflow.Delegation.Impl.Htmlformatter.DateInput, NetBpm" >
36
<parameter name="dateFormat">dd/MM/yyyy</parameter>
37
</htmlformatter>
38
</field>
39
<field attribute="leave days">
40
<name>请假天数(单位:天)</name>
41
<description>在此处填写您请假的天数(以后改进为系统自动计算)</description>
42
<htmlformatter class="NetBpm.Example.LeaveOfAbsence.TextIntergerInput, NetBpm.Example.LeaveOfAbsence" >
43
</htmlformatter>
44
</field>
45
<field attribute="comment">
46
<name>请假理由</name>
47
<description>在此处填写您请假的理由或者其他需要说明的信息</description>
48
<htmlformatter class="NetBpm.Workflow.Delegation.Impl.Htmlformatter.TextAreaInput, NetBpm">
49
<parameter name="rows">5</parameter>
50
<parameter name="cols">20</parameter>
51
</htmlformatter>
52
</field>
53
</activity-state>
54
55
<activity-state name="Request Cancel">
56
<image-coordinates>
57
<x1>287</x1>
58
<y1>146</y1>
59
<x2>420</x2>
60
<y2>178</y2>
61
</image-coordinates>
62
<field attribute="start date">
63
<name>开始时间</name>
64
<htmlformatter class="NetBpm.Workflow.Delegation.Impl.Htmlformatter.DateLabel, NetBpm" >
65
<parameter name="dateFormat">dd/MM/yyyy</parameter>
66
</htmlformatter>
67
</field>
68
<field attribute="end date">
69
<name>结束时间</name>
70
<htmlformatter class="NetBpm.Workflow.Delegation.Impl.Htmlformatter.DateLabel, NetBpm" >
71
<parameter name="dateFormat">dd/MM/yyyy</parameter>
72
</htmlformatter>
73
</field>
74
75
</activity-state>
76
77
<activity-state name="ChiefApprove">
78
<image-coordinates>
79
<x1>173</x1>
80
<y1>220</y1>
81
<x2>295</x2>
82
<y2>253</y2>
83
</image-coordinates>
84
<field attribute="requester">
85
<name>申请人</name>
86
<description>提交请假申请的员工</description>
87
<htmlformatter class="NetBpm.Workflow.Delegation.Impl.Htmlformatter.PersonLabel, NetBpm" />
88
</field>
89
<field attribute="start date">
90
<name>开始时间</name>
91
<htmlformatter class="NetBpm.Workflow.Delegation.Impl.Htmlformatter.DateLabel, NetBpm" >
92
<parameter name="dateFormat">dd/MM/yyyy</parameter>
93
</htmlformatter>
94
</field>
95
<field attribute="end date">
96
<name>结束时间</name>
97
<htmlformatter class="NetBpm.Workflow.Delegation.Impl.Htmlformatter.DateLabel, NetBpm" >
98
<parameter name="dateFormat">dd/MM/yyyy</parameter>
99
</htmlformatter>
100
</field>
101
<field attribute="Chief evaluation result">
102
<name>主管审批意见</name>
103
<description>请审批该员工的请假单</description>
104
<htmlformatter class="NetBpm.Workflow.Delegation.Impl.Htmlformatter.EvaluationInput" />
105
</field>
106
</activity-state>
107
108
<activity-state name="BossApprove">
109
<image-coordinates>
110
<x1>101</x1>
111
<y1>356</y1>
112
<x2>218</x2>
113
<y2>390</y2>
114
</image-coordinates>
115
<field attribute="requester">
116
<name>申请人</name>
117
<description>提交请假申请的员工</description>
118
<htmlformatter class="NetBpm.Workflow.Delegation.Impl.Htmlformatter.PersonLabel, NetBpm" />
119
</field>
120
<field attribute="chief">
121
<name>主管审批</name>
122
<description>同意该员工请假的主管领导</description>
123
<htmlformatter class="NetBpm.Workflow.Delegation.Impl.Htmlformatter.PersonLabel, NetBpm" />
124
</field>
125
<field attribute="start date">
126
<name>开始时间</name>
127
<htmlformatter class="NetBpm.Workflow.Delegation.Impl.Htmlformatter.DateLabel, NetBpm" >
128
<parameter name="dateFormat">dd/MM/yyyy</parameter>
129
</htmlformatter>
130
</field>
131
<field attribute="end date">
132
<name>结束时间</name>
133
<htmlformatter class="NetBpm.Workflow.Delegation.Impl.Htmlformatter.DateLabel, NetBpm" >
134
<parameter name="dateFormat">dd/MM/yyyy</parameter>
135
</htmlformatter>
136
</field>
137
<field attribute="Boss evaluation result">
138
<name>老板审批意见</name>
139
<description>该员工请假天数大于10天, 请您审批该员工的请假单</description>
140
<htmlformatter class="NetBpm.Workflow.Delegation.Impl.Htmlformatter.EvaluationInput" />
141
</field>
142
</activity-state>
143
</web-interface>
144
流程定义之动态委托类
委托类包含在lib文件夹下的程序集中。因为委托类数目众多,这里仅贴出几个典型的委托类, 其源码工程若需要请看这里:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

91

92

93

94

95

96

97

98

99

100

101

102

103

104

105

106

107

108

109

110

111

112

113

114

115

116

117

118

119

120

121

122

123

124

125

126

127

128

129

130

131

132

133

134

135

136

137

138

139

140

141

142

143

144

1. NetBpm.Example.LeaveOfAbsence.AutoSetAttributionsAction:该委托类设计为一个通用委托类,这里用来设置表识属性,如流程经过用户取消请假路径,则把RunTrace属性设置为requestercancel,供WhichWayDicision作判断用。


1

2

3

4

5

6

7

8

9



10


11

12

13

14

15



16

17

18

19

20



21

22

23

24

25



26

27

28

29

30

31

32

33

34

35

36

37

38

39

2. NetBpm.Example.LeaveOfAbsence.AnyOneJoin: 该委托主要用来设置激活父flow机制,这里是只要任何一条路径到达了join,则激活父flow,流程往下流。


1

2

3

4

5

6

7

8



9


10

11

12

13

14



15

16

17

18



19

20

21

22

23

24

25

26



27

28

29

30

31

32

33

34

35

36

3. NetBpm.Example.LeaveOfAbsence.WhichWayDecision:该委托根据流程实际流过路径,根据标识属性RunTrace等进行走哪条边的抉择,如注释。


1

2

3

4

5

6



7


8

9

10

11

12

13

14

15

16



17

18



19

20

21

22

23

24

25

26



27

28

29

30



31

32

33

34

35

36

37

38

后记
本文仅仅是一个示例,给大家提供一个运用nPdl定义NetBPM流程的参考,如果要把该流程投入现实中使用显然还需要做很多优化。该示例更多的是给大家展示流程定义中action可以定义多个, decision出来的transition可以到达同一条目的节点但名字却不一样等等. 示例中委托类的耦合性太高, 很多地方并不需要那样做, 如一个decision里面进行了2个判断: 判断是否大于10天, 判断是否审批同意, 这个完全可以拆分为两个独立的decision来做. 委托类设计成为高度可重用的, 耦合性低的通用类, 才是我们的目标.

NetBPM工作流的架构设计及实现浅析
组织架构组件(Oorganization Component)
读前的话:由于本文涉及内容颇多,若有地方读来不很明白,建议先跳过,整体上有个认识后,再回过头来理解。作者认识有限,若有错误,欢迎斧正:)原文地址: NetBPM工作流的架构设计及实现浅析(转载请保留)
NetBPM组件接口
NetBPM由一系列的组件构成,每一个组件都实现一个核心接口(采用Facade Pattern)。不同组件各自负责的核心功能根据WfMC规范而来。
NetBPM接口图:

- 为流程开发者提供的接口(Process Developer):该接口负责加载流程定义到NetBpm引擎。首先,流程开发者依照nPdl创建一个流程定义,并将其打包成流程定义压缩包(该包包含一个业务流程的所有信息),然后通过NetBPM Web界面或者是其他方式把流程定义压缩包加载到NetBPM引擎,在加载过程中流程定义压缩包将会被解析被保存到NetBPM数据库中。
- 为用户提供的接口(User):这里用户表示执行流程的人。流程运行主要有2种行为:开始一个流程和执行一个活动(activity)。开始一个流程将创建该流程的一个流程实例,一个流程实例对应流程定义的一次执行。一个流程实例包含了一个或多个并行的flow-of-execution(见flow)。对于处在activity-state(活动节点,见nPdl)的flow,系统一定会指派一个具体的人(或者组)或者第三方来执行活动(activity)。执行活动是Execution Interface的第二种行为。当然了,运转接口还会实现一些其他的方法如获取任务列表,获取有效流程定义列表等。
- 外部IT系统(External IT Systems)和NetBpm引擎之间的接口:外部IT系统能以2种方式实现和NetBPM的交互。
- 系统发起交互:系统直接和外部IT系统交互。当系统想要触发流程中的某一个action时,它必须使用上面提到的Execution Interface。
- 流程发起交互:对于流程发起交互这种类型来说,需要有Interactors。这些Interactors是流程定义的一部分,它包含在流程定义包内(实际上Interactors就是能够访问FlowContext的.NET程序集,也就是我们后面要说到的包含委托类的程序集合)。Interactors在FlowContext(FLOW运行上下文环境)和外部IT系统之间建立起了通信渠道。
- 组织架构数据(Organisational Datastore)和NetBpm之间的接口:仔细想想,NetBpm是不是还缺了点什么,没错,那就是组织架构的信息:比如说人、团队、部门、角色等。因为在现实情况中,对于不同的组织结构,组织架构信息可能被保存在不同类型的数据库中,如LDAP系统,关系型数据库等。为了让NetBPM能够在一个现实的组织架构中实现快速部署,NetBPM把所有的组织架构信息都聚集在一个组件(Organisation Component)中。这种做法也就是我们通常说道会话门面模式(Session Façade Pattern),它使得NetBPM访问来自不同数据源的组织架构信息变得更为简单。
下面我们逐步介绍NetBPM的各个组件,下面是NetBpm组件结构图:

定义组件(Definition Component)
该组件实现核心接口IDefinitionSessionLocal,用来解析、加载流程定义压缩包,并将其保存到数据库中。此外,它还提供获取某个流程定义,获取所有有效流程定义列表等和流程定义相关的方法。
运转组件(Execution Component)
该组件实现了核心接口IExecutionSessionLocal,它是NetBPM引擎推动核心,如前面如述,它主要实现2个方法:开始一个流程实例(Start ProcessInstance)和执行一个活动(Perform Activity)。另外,它还提供获取用户任务列表,取消流程实例等辅助流程运转的方法。
组织架构组件(Organization Component)
该组件实现接口IOrganisationSessionLocal,它把所有的组织架构信息都聚集在一起,包括Users、Groups和Memberships。运转组件在为activitie-state指定执行者时,需要有关user和group的信息。这些信息将以只读模式由组织架构组件向运转组件提供。下面是NetBPM默认的组织架构组织数据模型。在此基础上,我们可以很方便的实现自定义的适合实际项目需求的组织架构组件,以和我们的用户数据库或者是LDAP系统相关联。
NOTE:NetBPM源码中实现的只是一个简单的组织架构模型,但它提供了一种思考方向,我们可以很方便在此基础上进行扩展来实现满足切实需求的自定义组织架构组件:)
日志组件(Log Component)
该组件实现接口ILogSessionLocal,用来记录工作流引擎的操作痕迹,象属性值的更新(如用户提交的表单被上级打回重新填写,那么就会出现多次表单数据,这就是一种属性更新),委托类的调用情况等都会被记录下来保存到数据库中。
任务调度组件(scheduler component)
该组件实现接口ISchedulerSessionLocal,在现实的业务流程中,我们经常会遇到需要定时触发某个任务的需求,任务调度组件正是作用于此。引擎或者是第三方把需要在某个时刻执行的任务信息(包括任务执行环境、执行时间等)封装成Job保存到数据库中。任务调度组件将按照指定的时间间隔不停的扫描任务表,根据执行时间对比来执行定时到了的Job。
管理监控组件(Admin Component)
该组件用来对流程定义,流程实例执行情况等进行监控。(源码待完善)下面是NetBpm核心项目在Visual 该组件用来对流程定义,流程实例执行情况等进行监控。(源码待完善)
下面是NetBPM核心项目在Visual Studio解决方案中的源码结构图,所有组件都包含在Workflow文件夹下,每一个文件夹分别对应实现了一个核心组件。

NetBpm中的几个重要概念
flow
flow不知道翻译为什么好,在JBPM中叫做Token,翻译为令牌,这里我们就叫做flow吧。它代表activity-states(活动节点,见nPdl)的一个“thread of execution”,相当于是一次流程实例执行过程中在流程定义模板中的令牌(还真难描述清楚,看下面一起理解:))。前面说了,一个流程实例代表一个流程定义的一次执行。如下图所示,流程实例的状态可以看成是一颗flows树。
当开始一个流程实例后,在start-state(开始节点,见nPdl,start-state实际上可以看做是一种特殊的activity-state)引擎将自动产生一个名为root的flow。flow中包含了该流程实例的相关信息,如属性值、流程定义信息、flow所在activity-state的执行者等。root flow在遇到fork(分散节点,见nPdl)之前,将更新其带有的实时信息(如执行者、属性值等),这些实时信息随着流程的运转而变化。遇到fork后,根据ForkHandler委托类,root flow将分散成若干(>1个)forked flow(我们可以把root flow称为这些forked flow的父flow)。若是分散为多个,则此时forked flow将并行运行,父flow则暂时退隐,只至到join(汇聚节点,见nPdl)汇聚,引擎将根据join定义的JoinHandler委托类来确定激活父flow的机制。
NOTE:关于fork和join机制,请参考nPdl fork、join小节一起理解。
attributes(属性)
attribute用来表示流程实例中的变量。一个attribute-instance(属性实例,也就是属性值)代表一次流程实例执行过程中对应属性的实例。属性一般有几种,一种是activity-state(包括start-state)需要用户或者第三方来填写(更新)的属性(一般对应用户Web界面表单上要填写的值),一种是角色对应的属性,还有一些用作标识属性(用来存储某些信息以方便后面的节点运用这些信息处理逻辑判断)。
- serializer(属性的序列化):
Serializer和HtmlFormatter都是委托类,Serializer负责把属性实例在.NET-Objects和文本两种状态间进行转换,以方便把属性值存入和取出数据库。而Htmlformatter主要用来联系属性值和Web Forms显示时对应的http-text。 - attribute的范围:attribute-instances(属性实例)和flow有关。在process-definition(流程定义根节点,见nPdl)节点中定义的attribute和root-flow关联,而在concurrnet-block(并行运行块,见nPdl)中定义的局部attribute实例则和forked flow关联。一个flow能够“看见”和它关联的所有属性以及该flow的父flow的所有属性。
引擎运行时上下文环境(Execution Context)

因为图片太大,关于继承IHandlerContext的接口关系图查看点击这里
ExecutionContext(ExecutionContextImpl类型的对象,我们暂且翻译为运行时上下文环境:)) 包含了引擎在运行时和流程实例相关的所有有用信息(上文中提到的flowcontext就是一种ExecutionContext),它在委托类(包括流程定义压缩包中程序集中定义的委托类)和流程引擎之间建立起了相互联系的渠道,这是非常关键的。如上面ExectutionContext 类图所示,ExecutionContextImpl实现了下面这些接口:IAssignmentContext、IDecisionContext、IForkContext、IActionContext、IJoinContext、IProcessInvocationContext、ITaskContext,这些接口都是为匹配特定的委托类而设计,它们规范了一种特定的上下文环境,如IActionContext匹配action类型委托类,IDecisionContext匹配DecisionHandler类型委托类等,而ExectutionContext是所有这些特殊的运行时上下文环境的一个综合。当引擎在运转组件把ExectutionContext作为参数传送到具体类型的委托类时(关键:这就是委托类和流程引擎建立联系的方式),ExecutionContext对象将“拆箱”成为特殊的Context,如:把ExecutionContext对象传给action类型的委托类Run()方法时,ExecutionContext对象将拆箱为ActionContext对象以限制其能够调用的方法。
如“继承自IHandlerContext的接口”图中所示,这些接口都继承了同一个接口IHandlerContext。IHandlerContext是一个规范了最基本的委托类处理上下文环境的接口,实现该接口的继承几口也就都要实现IHandlerContext中定义的方法,当然每一种继承它的特定接口又都可以具有其特定的方法。我们先看公共接口IHanlderContext类图:如上IHandlerContext接口图所示,这些接口都继承了同一个接口IHandlerContext。IHandlerContext是一个规范了最基本的委托类处理上下文环境的接口,实现该接口继承接口的类也就都实现了IHandlerContext中定义的方法。当然,每一种继承它的特定接口又都可以具有其特定的方法。我们先看公共接口IHanlderContext类图:
大多数的方法,我们从方法名称就可以看出其具体作用了,这里重点介绍下GetAttribute()方法和GetConfiguration()方法,这是我们在写委托类实现时,要经常用到的2个方法。GetAttribute()用来获取流程实例中的属性值,包括流程前面处理者产生的属性值(如用户填写表单的值)和前面处理引擎事件中设置的表示属性值(注:IActionHandler具有SetAttribute()方法,该方法经常用来标识属性值,供后面程序逻辑用)等。而GetConfiguration()用来获取流程定义中设置的parameter(参数,请nPdlparameter小节)。
下面再来看几种典型的特定上下文环境接口:
- IAssignmentContext:为IAssignmentHandler委托类提供引擎上下文环境,该接口定义了获取组织架构信息的方法,具体实现方法见类图。
- IActionContext:为action提供引擎上下文环境,注意其具有SetAttribute的方法,可以为流程实例中的属性赋值,具体实现方法见类图。
- IForkContext:为IForkHandler委托类提供引擎上下文环境,它定义了如何从父flow分散forked flow的方法,见类图。
- IJoinContext:为IJoinHandler委托类提供引擎上下文环境,它定义了获取其他并行flows的方法,见类图。
- IProcessInvocationContext: 为IProcessInvocationHandler委托提供引擎上下文环境,实现方法见类图。
- 其他的委托类型上下文环境除了实现基本IHandlerContext方法外,没有特定的方法,请参考IHandlerContext方法。
委托类
在前面我们一直提到委托类,那么委托类到底是什么呢?这里委托的概念指的不是.NET Framework中delegate,这里可以理解它为“委托、代为处理”这样的概念就好。
NetBPM被设计成通用的流程处理引擎,NetBPM核心执行引擎只负责处理最基本的业务流程逻辑,所有不定的逻辑都被委托给一系列的接口,这些接口称作Delegation Interfaces(委托接口),而实现这些接口的类就是委托类。流程定义约定在什么场合使用什么委托类型,引擎和委托类如何关联也在流程定义中完成。
为了达到最大的可扩展性,流程开发者在流程定义时可以选择下面任意一种委托类实现方式:
- 使用已经在NetBpm引擎中实现的有效的委托类。(具有通用性的委托建议采用这种方式在引擎中定义,以便重用)
- 使用自定义的委托类,以.NET程序集的形式通过流程定义压缩包动态加载。
正是方式2这种形式给NetBPM带来了极大的灵活性,把只适合于某个特定流程的的程序逻辑(这些往往占了大多数)以.NET程序集的形式定义在流程定义压缩包中,NetBpm通过流程定义组件将其解析并保存至数据库。当引擎运转流程需要调用委托类时,引擎利用反射技术实例化出委托类对象,然后利用上文介绍的运行时上下时环境(ExecutionContext)建立起委托类和引擎之间的交互渠道,这真是一个令人兴奋的设计:)
下面是NetBpm中的委托类型(建议和ExecutionContext一节一起理解):
- AssignmentHandler:
实现接口IAssignmentHandler, 它可以和组织架构中的IT-System相互通信,用来为activty-state指定执行者。IAssignmentHandler具有唯一的方法SelectActor(IAssignmentContext assignerContext),其中的assignerContext是其和引擎联系的渠道; - ActionHandler:实现接口IActionHandler,它用来执行一段流程引擎外完成的程序逻辑,关于action在哪里执行,什么时候执行等在流程定义中被定义。actions可以被看作一系列流程执行事件的侦听接口,它具有方法Run(IActionContext actionContext),actionContext是它和引擎联系的渠道。
- DecisionHandler:实现接口IDecisionHandler,它用来选择决定走哪一条transition(边,见nPdl),具有方法Decide(IDecisionContext decisionContext),该方法返回选择要走的边的名称,decisionContext是其和引擎联系的渠道。
- ForkHandler:实现接口IForkHandler,当执行到fork节点时,Forkhandler用来决定从fork流程的边中哪些边需要“forking”。它具有方法Fork(IForkContext forkContext),forkContext是其和引擎联系的渠道。注:forkhandler可以在同一条边上分散多个forked flow。
- JoinHandler:实现接口IJoinHandler,当执行到join时,JoinHandler决定是否要激活父flow,它具有方法Join(IJoinContext joinContext),joinContext是其和引擎联系的渠道。
- Serializer:实现接口ISerializer,具有方法Serialize(Object valueObject)和Deserialize(String text),该委托接口负责用来转换属性值,把属性值在.NET-objects(.NET对象)和text-format(文本)之间转换。其中,text-format(文本)用来存储属性值到数据库中。
- HtmlFormatter:实现接口IHtmlFormatter,该委托主要用来在属性值和其相对应的web界面元素之间建立联系,同时负责解析web界面元素具有的值。若移植到ASP.NET平台,该接口建议重写。
- ProcessInvocationHandler:实现接口IProcessInvocationHandler,该委托实现收集子流程的初始化数据、收集处理结果、指定完成子流程后要流入的边等方法,processInvocationContext是其和引擎联系的渠道。
................///.............是否要添加委托类的例子
流程定义版本问题
流程定义的名称与版本
包含在一个流程定义压缩包中的信息叫做流程定义。NetBpm中,流程是由字段name来区分的,也就是说引擎根据流程的name来判断两个流程是否相等。在流程定义包中不能指定版本,当一个流程定义被引擎加载后,NetBpm将检查是否有该流程定义的旧版本。如果有,NetBpm将自动设置该新加载进来的流程版本为所有存在的旧版本流程定义中最高版本数目基础上加1。
流程执行与版本
当调用运转组件获取流程定义列表方法时,只能获取到每个流程的最高版本流程定义。这样做保证了用户总是从最新版本的流程定义开始一个流程实例。当新的流程版本加载到NetBpm时,所有正在运行的旧版本的流程实例将保持在原来流程定义方式下运行。
委托类与版本
关于版本的另外一个方面是委托类。不同版本流程定义的委托类不是共享的,也就是说每个流程在执行时只会“看到”它自己流程定义的委托类。
异常处理机制
NetBpm作为一个集成平台,当流程运行时,肯定会依赖公司很多其他的IT资源,一旦这些依赖导致流程执行时出现错误,NetBpm提供了3种解决机制:
- 忽略错误
- 把错误日志记录下来(默认采用的机制)
- 错误执行回滚操作(rollback)
执行回滚机制中,流程实例将会被回滚到执行activity之前的状态。如果是对NetBpm 调用Eecution Interface时发生流程错误,所有的流过的transition(边)都会执行回滚。
流程定义元素类图
关于流程定义的详细情况,参见nPdl。
NetBpm中使用的框架或组件
NetBpm中用到的框架、组件、工具比较多,它们大都是优秀的开源项目。如Castle,NHibernate,Log4Net, NVelocity,NUnit,NAnt等,不要被这些框架吓倒,实际上,它们仅仅只是“框架”而已:)
IOC容器――Castle
NetBpm使用了Castle框架,主要用它来实现IOC(控制反转或者说依赖注入),以依赖注入的方式加载核心组件,如DBClassLoader,流程定义组件,运行组件,日志组件,组织架构组件,任务调度组件等。在Web程序启动的时候,根据配置文件,所有的核心组件都将注册到Caslte IOC容器中,以后当需要使用某个组件的时候,只需利用系统提供的ServiceLocator(服务加载工具类)从容器中获取实例即可。另外,在任务调度组件中也有用到Castle的Startable Facility(注:大家把facility理解为注入性质的,对Castle IOC内核容器的功能扩充组件。Castle本身自带实现了一些faciltiy,开发者也可以自定义facility),该facitlity主要用来自动运行程序(这里用来自动间隔扫描任务表,进行任务调度)。
Castle是.NET平台下一个功能强大的优秀开源框架,关于Castle的更多信息,请看Castle官方网站。另外TerryLee的博客中关于Castle的中文资源也很丰富。
数据持久层―― NHibernate
NetBpm中NHibernate组件是作为Castle的一个facility存在的,它用来实现NetBpm数据持久层, 并方便的实现了事务支持。关于NHibernate的更多信息,请看NHibernate官方网站,关于NHibernate作为Castle的facility相关请看这里。
示例web层――MonoRail
大家在Demo演示体验的时候,一定很奇怪,没有看到熟悉的.aspx页面,而是.rails页面,为什么呢?答案就是NetBPM的Web层采用的是MonoRail框架,而不是我们熟悉ASP.NET框架。MonoRail是Caslte框架下针对web层编程的一个子框架,它从Ruby Rails获取灵感而来,采用架构清晰分工明确的MVC模式。NetBPM采用的使用NVelocity作为页面解析引擎的MonoRail,它只是对NetBPM核心API的一个Web界面演示示例,用来告诉我们该怎样从Web层调用NetBpm API。所以,虽然MonoRail有其独到之处,但是在其被普及并有好用工具支持之前,我们只需简单了解下它的运行机制,用以熟悉web层如何利用NetBpm API,不需要了解它太多。用我们熟悉的ASP.NET实现Web部分显然是更好的选择:)。关于MonoRail的更多信息,请看这里。
系统日志 ――Log4Net
log4net大家一定不陌生了,NetBpm使用它来记录系统日志,关于log4net的更多信息,请看Log4Net官方网站,网上中文资源也很丰富.
单元测试工具――NUnit
单元测试工具NUnit一直是大家用来单元测试的利器。 NetBPM源码中已经建有几个测试工程。关于NUnit的更多信息,请看NUnit官方网站。
注:移植到.NET Framework 2.0下,可能要更改其版本。
待写:NetBPM工作流nPdl详解,一个NetBPM现实生活中请假审批示例
后记
NetBPM的设计无疑是巧妙的,但是现阶段的它显然还不是一个完美的工作流引擎,缺乏如JBOSS这样的强大后盾作支持,中途又遇上强敌WF,NetBPM远没有其兄弟JBPM风光,更新没有它快(JBPM已经出3.0版本了),获取的支持也少许多, 但是.NET平台下能有这样一个优秀的开源工作流项目是十分可贵的,如果您正在WF中苦苦挣扎,也许,开源的NetBPM将带给您一个惊喜:)


