1. 关于工作流
工作流是由不同的人做出的一系列决策,根据定义和可重复的过程,确定其中一个人所做出的特定请求会发生什么。
2. 工作流过程(Process)的配置和用户
在我们可以在此工作流引擎数据库中设计任何其他内容之前,我们首先需要定义确切构成流程的内容,以及我们的用户是谁以及哪些人可以修改流程本身。
Process (流程表) 设计
可能包含的字段:
名称 | 描述 | 类型 |
---|---|---|
id | id | integer |
name | 流程标识 | string |
该表虽然非常简单,但却是其余设计的中心参考点; 此数据库中的大多数表(例如,状态(State),转换(Transition),请求(Request)等)都需要直接或间接地与此表相关联。
User (用户) 设计
在不同系统中设计可不一样
3. 工作流状态(State)和转换(Transition)的设计
在一次工作流程中会设计到不同的状态(state), 状态之间的流转需要不同的程序, 我们将之命名为转换(transition)
状态(State)的设计
工作流中的不同状态各有不同的属性类型, 在此总结出几种常见的状态类型枚举:
- 开始(start):每个进程只应该一个。此状态是创建新请求时所处的状态。
- 正常(normal):没有特殊名称的常规状态。
- 完成(complete):表示此状态下的任何请求已正常完成的状态。
- 拒绝(denied):表示此状态下的任何请求已被拒绝的状态(例如,从未开始且不会被处理)。
- 已取消(cancelled):表示此状态下的任何请求已被取消的状态(例如,工作已开始但尚未完成)。
一个状态在一次流程中是唯一的,每个状态都有名称,描述和类型。
名称 | 描述 | 类型 |
---|---|---|
id | id | integer |
process_id | 所属流程的id | integer |
name | 状态名称 | string |
state_type | 状态类型 | string |
description | 状态描述 | string |
转换(Transition)的设计
流程中不同的状态之间存在状态的转换(Transition)
转换在进程下也是唯一的,标记着从一个状态到另一个状态的过程, 因此转换由主键,进程ID,当前状态和下一个状态组成:
名称 | 描述 | 类型 |
---|---|---|
id | id | integer |
process_id | 所属流程的id | integer |
current_state_id | 当前状态id | integer |
next_state_id | 下一状态id | integer |
4. 工作流操作动作(Action)和后续事件(Activity)的设计
状态(State)之间的流转我们称之为转换(Transition), 那么促成这个转换发生的动作和转换发生后的后续影响事件.我们成为:
- 动作(Action): 用户在某个状态节点可操作的行为
- 活动(Activity): 转换到状态节点后产生的事件
动作(Action)的设计
同样的不同的动作也各有不同的属性类型, 在此总结出几种常见的动作类型枚举:
- 批准(approve):操作人将请求应移至下一个状态。
- 拒绝(deny):操作人将请求应移至上一个状态。
- 取消(cancel):操作人将请求应在此过程中移至“已取消”状态。
- 重新启动(restart):操作人将将请求移回到进程中的“开始”状态。
- 解决(resolve):操作人将将请求一直移动到Completed状态。
名称 | 描述 | 类型 |
---|---|---|
id | id | integer |
process_id | 所属流程的id | integer |
action_type | 操作类型 | string |
name | 操作名称 | string |
description | 操作描述 | string |
这里需要提及的一点是,现实工作中往往会存在着一次的审批需要多人协作上下级协作后才会生效的情景,或者是多个不同动作可以达到同样的效果.所以设计中是允许多个动作对一次转换进行操作的, 即转换(Transition)和动作(Action)之间是多对多的, 因此:
引入中间表 TransitionAction
名称 | 描述 | 类型 |
---|---|---|
transition_id | transition_id | integer |
action_id | action_id | integer |
此外, 活动(Activity)是标记转换到状态节点后产生的事件.
可以是当工作进入到了某个状态, 也可以是完成了从某个状态到某个状态的转换触发的.
活动(Activity)的设计
活动(Activity)本身的设计和动作(Action)结构类似, 不同的是在不同系统中Activity是可以被多态关联到不同的动作事件的, 在这就不引申出去讲.
回到事件本身, 可以是由state触发的,也可以是Transition触发的, 他们之间亦是多对多的关系
设计表: 略
5. 工作流请求(Request)的设计
在我们的工作流引擎中,将请求定义为:在某一个流程下由某个用户发起的一次申请, 请求具有以下基本部分:
- 基本信息:标题,创建日期,创建用户和当前状态ID。
- 数据:与单个请求相关的可变数据集。
- 利益相关者:一组将接收有关请求的定期更新的用户。
- 文件:与单个请求相关的任何文件。
- 备注:用户输入的与个人请求相关的任何注释。
- 请求操作:可以在请求的任何给定时间执行的操作
请求(Request)的设计
请求是在流程下的一次操作所以表的设计可能包含:
名称 | 描述 | 类型 |
---|---|---|
id | id | integer |
process_id | 关联流程的id | integer |
title | 请求的标题 | string |
user_id | 请求的用户 | integer |
current_state_id | 请求的用户 | integer |
requested_at | 请求的时间 | datetime |
在不同的工作流程下, 请求需要发送的数据可能都不一样, 所以针对请求数据(RequestData)也做了一层包装:
名称 | 描述 | 类型 |
---|---|---|
id | id | integer |
request_id | 所属请求的id | integer |
key | 数据的key | string |
value | 数据的value | string |
一次的请求也可能要包含通知的对象(User).利益相关者(RequestStakeHolder)
名称 | 描述 | 类型 |
---|---|---|
request_id | 所属请求的id | integer |
user_id | 所属用户的id | integer |
当然一次完整的工作流程不只包含了这些, 它可能会有关联的文件(Attach), 备注(Note), 这些都可以用基础的附件,备注对象多态关联到各个步骤下所需要的地方去.
6. 工作流请求操作(RequestAction)的设计
到目前为止,我们建立的所有基础设施都已经基本完成。最后,我们可以构建模式的最后一部分:Request Actions表。
请求操作(RequestAction) 的设计
请求操作(RequestAction)
我们现在拥有用户可以执行的操作来调用Transitions, 当然请求是不能执行任何操作的, 只执行我们配置的范围内的动作.
让我们先看看RequestActions表的模式,然后展示它将如何实际使用:
名称 | 描述 | 类型 |
---|---|---|
id | id | integer |
request_id | 所属请求的id | integer |
action_id | 所属操作的id | integer |
transition_id | 所属转换的id | integer |
is_active | 是否可执行 | boolean |
is_complete | 是否已完成 | boolean |
我们是如何来使用request_action的呢
- 当请求进入状态时,我们从该状态获得所有符合的转换。对于那些Transitions中的每个Action,我们构建出RequestAction对象,且is_active = true,is_completed = false。
- 用户可以随时提交动作。每个提交的Action都包含一个RequestID和一个UserID。
- 提交操作时,我们检查指定请求的RequestActions。如果提交的Action与其中一个(is_active = true)的活动RequestActions匹配,设置 is_active = false 和 is_completed = true。
- 将提交的操作标记为已完成后,我们将检查该请求(Request)中该转换(Transition)的所有操作(RequestAction)。如果所有RequestActions都标记为已完成,那么我们将禁用所有剩余的操作(通过设置is_active = false,例如,未匹配的Transitions的所有操作)。
实例演示:
工作流有
状态(State): A(开始), B(同意), C(拒绝)
转换(Transition): A -> B(ID 1),A -> C(ID 2),B -> C(ID 3)
过渡行动(Action):
A - > B:申请人批准(ID 1)并由主管批准(ID 2)
A - > C:被高管拒绝(ID 3)
B - > C:被请求者拒绝(ID 4)
创建
申请人甲创建了一个请求(ID 1), 该请求立即被置于A状态。
此时, 系统将查找状态A的所有转换,并找到其中两个转换,转换1和2.然后,它将以下数据加载到RequestActions中:
请求ID | 动作ID | 转换ID | is_active | is_completed |
---|---|---|---|---|
1 | 1 | 1 | true | false |
1 | 2 | 1 | true | false |
1 | 3 | 2 | true | false |
现在请求只是处于当前状态,等待提交动作。
提交
当甲提交申请后, 状态流转到A, 变成了可以同意可以拒绝的形态.
请求ID | 动作ID | 转换ID | is_active | is_completed |
---|---|---|---|---|
1 | 1 | 1 | false | true |
1 | 2 | 1 | true | false |
1 | 3 | 2 | true | false |
批准
领导乙批准了甲的申请
请求ID | 动作ID | 转换ID | is_active | is_completed |
---|---|---|---|---|
1 | 1 | 1 | false | true |
1 | 2 | 1 | false | true |
1 | 3 | 2 | true | false |
注意,此时因为转换Transition 1 的两个动作都已完成,我们现在必须遵循转换1并将请求移动到下一个状态,即状态B.在我们转到状态B后,我们从该状态加载转换的动作并禁用任何旧的行动;
即,状态流入B后, A->B 之间的Transition(1) 的所有Action(1, 2) 都被完成了, is_completed = true
并且, 处于状态A的所有Action(1, 2, 3)都被禁用了. is_active = false
请求ID | 动作ID | 转换ID | is_active | is_completed |
---|---|---|---|---|
1 | 1 | 1 | false | true |
1 | 2 | 1 | false | true |
1 | 3 | 2 | false | false |
1 | 4 | 3 | true | false |
7. 结语
以上就是关于工作流引擎的表结构设计