1 背景
随着业务的爆发式发展,现在很多业务系统为保证业务操作的准确性,要求实现多录多核机制,而这些业务很多不适合放在业务参数配置中心。为方便各个业务系统快速实现多录多核机制,避免出现一些业务因为要求多录多核而迁就的放在业务参数配置中心,造成业务参数配置中心慢慢的变成一个“大杂烩”,而丧失去了其系统本身的定位。
另外,随着新系统建设周期结束,很多系统也开始进行业务梳理及收敛,整理业务的标准流程。通过流程驱动的方式进行高效率的设计、开发、上线。
工作流就是流程驱动模式的一个载体。工作流是对每个节点上进行行为、过程、结果的管理,是将业务进行标准化、规范化、流程化的惯用方式,同时也能够降低单位成本、提升高质量。前提是需要深刻理解业务、进行业务类型收敛。收敛业务类型,是为标准化做基础。
2 设计思想
基于“四要四不要”思想来进行多录多核组件的设计。
- 要考虑业务的差异性,不要拿着锤子找钉子
- 通过SPI机制,满足业务差异性
- 现阶段只封装一些实际业务使用的组件及部分通用组件,针对未来系统可能使用的组件,用时再封装。封装时考虑业务原子性。
- 要能够实际落地解决问题,不要让设计停留在表面
- 结合业务现阶段及后期规划,收集并抽象化,具体实施
- 避免空泛的设计。
- 要结合现有技术栈,不要盲目追求框架的先进性
- 现在CC所使用Activiti实现多录多核,基于Activiti框架基础之上进行更贴合业务的组件封装。在使用Activiti时也踩过一些坑,在后面设计及实现时可以避免。
- 现在更高性能的工作流Camunda框架,需要基础框架SpringBoot升级到2.X版本。
- 要张弛有度,不要紧紧贴合
- 既然是在框架基础之上做组件,就要比框架更贴近实际业务。
- 既然是组件,就具有一定的通用性,就不能紧紧耦合业务。
另外,后续若需对应的流程图、轨迹图等,可以基于Activiti现有的流程图直接拿来使用。并不需要过多的开发。
总之,站在巨人的肩膀上,做一些方便业务使用的组件更高效、高质,方便以后的扩展。
3 逻辑结构图
图是思想的结晶,先上图:
3.1 业务场景抽象
基于现有业务及后续业务收敛,需要进行业务场景抽象。只有完成这一步,才能更高效、高质量的进行驱动业务发展。
业务场景抽象大体分三类:
- 通用内容场景模型
组件应该支持度很高。业务参与扩展更少。
- 业务领域场景模型
组件支持度较高,业务有一定的扩展。
- 个性化定制场景模型
可以基于节点层的相应组件进行拼装,业务也需要相应的扩展。
3.2 流程模版
方便业务快速、便捷接入,提供流程模版进行CLONE。
3.3 SPI机制
相应的扩展部分提供默认的实现,若默认实现不满足具体业务,可以由具体业务进行实现相应的扩展接口,并把扩展实现进行注册。
3.4 适配
基于Activiti框架所提供的一些默认实现,并不满足实际需要,通过适配来达到实际使用时的功能。比如:分布式支持、实际业务中的人员、部门等组装。
3.5 节点封装
基于Activiti框架所提供的一些节点,把这些节点进行贴合实际业务、业务组件所需要的进行封装。业务使用人员或者配置人员,并不需要关注Activiti框架的节点的底层含义。
3.6 事件机制
分流程事件与节点事件。
流程事件分:流程发起、流程结束等
节点事件:节点发起、节点创建、节点完成等
支持多事件机制。
3.7 消息机制
结合事件机制,进行消息机制的支撑。
可考虑的消息有:待办、自定义(短信、邮件、站内消息、钉钉、微信)实现等
4 程序结构图
5 基础配置表
管理业务相关流程,需要做一些维护性信息。主要表结构如下:
5.1 ES_PROC流程基础信息
字段 | 类型 | 描述 | 备注 |
PROC_ID | VARCHAR2(32) | 主键 | |
PROC_NAME | VARCHAR2(200) | 流程名称 | |
PROC_GEN | VARCHAR2(200) | 流程模型生成器 | |
PROC_BIZ_ID | VARCHAR2(32) | 归属业务所属ID(暂加) | |
PROC_GEN | VARCHAR2(200) | 流程生成器 | 提供默认组件的生成器,若不满足,可自行扩展。 |
PROC_STATUS | NUMBER | 状态 | 0:不可用,1:可用 |
PROC_CONST_DATA | VARCHAR2(4000) | 流程常量,内置在流程中 | JSON格式, 默认均是字符串,若是其他类型,请参考 KeyValue模式 |
CREATE_TIME | DATE | 创建时间 | |
MODIFY_TIME | DATE | 修改时间 | |
CREATE_USER | VARCHAR2(100) | 创建人 | |
MODIFY_TIME | VARCHAR2(100) | 修改人 |
5.2 ES_PROC_NODE流程节点信息
字段 | 类型 | 描述 | 备注 |
PROC_NODE_ID | VARCHAR2(32) | 主键 | |
PROC_NODE_NAME | VARCHAR2(200) | 节点名称 | |
PROC_NODE_TYPE | NUMBER | 节点类型 | 0:多录,1:审核,2:或签,3:会签,4:任务节点,5:网关,6.. |
NODE_CONFIG | VARCHAR2(4000) | 节点配置,正常流程需要配置的一些信息,供流程生成以及流程驱动时使用。方式:JSON串,具体配置有具体说明 | 待补充 |
IDX | NUMBER | 节点顺序 | |
PROC_ID | VARCHAR2(32) | 流程配置ID | |
ROLL_BACK_CONFIG | VARCJAR2(4000) | 回滚配置信息。 | JSON格式,具体类型节点具体配置 |
CREATE_TIME | DATE | 创建时间 | |
MODIFY_TIME | DATE | 修改时间 | |
CREATE_USER | VARCHAR2(100) | 创建人 | |
MODIFY_TIME | VARCHAR2(100) | 修改人 |
5.3 ES_CUSTOMIZE_HANDLER_CONFIG(自定义执行配置表)
字段 | 类型 | 描述 | 备注 |
CUSTOMIZE_HANDLER_CONFIG_ID | VARCHAR2(32) | 主键 | |
CUSTOMIZE_HANDLER_CLASS | VARCHAR2(200) | 自定义执行器 | 全类名模式 |
CUSTOMIZE_HANDLER_TYPE | VARCHAR2(50) | 自定义执行器类型 | 枚举模式 |
PROC_ID | VARCHAR2(32) | 流程ID | |
STATUS | NUMBER | 状态,0:不可以,1:正常可用 | |
CREATE_TIME | DATE | 创建时间 | |
MODIFY_TIME | DATE | 修改时间 | |
CREATE_USER | VARCHAR2(100) | 创建人 | |
MODIFY_TIME | VARCHAR2(100) | 修改人 |
6 自研
以上设计是基于Activiti框架基础之上做的设计,但是此Activiti框架底层依赖23张表,这一点是比较重的。
决策选型是基于成本与收益进行权衡的。
基于以下问题域进行比较:
优缺点 | Activiti | 自研 |
扩展性 | 专业的工作流引擎,很全面, 扩展性高 | 基于现有的业务需求设计出的组件可能会有一定的局限性,后期想扩展的话,会有些影响。 |
组件质量 | 基于Activiti的使用有一定的积累,踩过坑、补过坑。单说表的数量:重一些。 | 透明,更轻量级。 |
掌握度 | 掌握程度待加强 | 完全能够掌握 |
研发效率 | 无需改动已有的结构,新的业务组件可以快速支撑,预计每个组件基本2~5天可搞定。 | 完全自研,周期较长 |
表结构 | 框架自带23张 | 8张 |
6.1 自研思路
参考Activiti的设计理念,自己实现PVM模型解析及流程驱动。相应的表中存储运行中与历史的数据(Activiti中存储的为提高运行效率,运行中与历史的相关数据进行分开存储。历史存储的相关表结构到达:8张)。
6.2 逻辑结构图
自研分两部分,第一部分为自研流程框架,第二部分为多录多核的封装。
6.2.1 自研工作流(ES_PROC)逻辑结构图:
主要模式为:领域模型+命令模式
6.2.2 组件封装逻辑结构图
6.2.3 重点考虑点
- 业务与流程的事务(支持可配置化)
业务办理成功|失败 对流程节点流程的影响。
流程节点流转时的正常|异常,对业办理的影响。
解决方案:在拦截器层,处理事务。根据业务实际的事务进行处理。默认为REQUIRED模式。
6.3 表结构设计
主要表结构如下:
6.3.1 EX_PROC_INST(流程实例):发起一个流程,就是一个流程实例
字段 | 类型 | 描述 | 备注 |
PROC_INST_ID | VARCHAR2(32) | 流程实例ID | |
PROC_STARTER_ID | VARCHAR2(100) | 流程发起人(只有父流程有此字段) | |
INST_STATUS | NUMBER | 流程实例状态 | 0:进行中,1:结束,3:强制终止。 |
PROC_BASE_ID | VARCHAR2(32) | 所属流程定义ID | |
INST_START_TIME | DATE | 流程实例开始时间 | |
INST_END_TIME | DATE | 流程实例结束时间 | |
KEY_WORDS | VARCHAR2(4000) | 实例名称,关键词 | |
ELAPSED_TIME | NUMBER | 耗时(ms) | |
END_NODE | VARCHAR2(32) | 结束时的节点 | |
PROC_INST_NAME | VARCHAR2(200) | 流程实例名称 |
6.3.2 ES_PROC_EXEC(流程执行实例):流程实例中的具体执行实例。
字段 | 类型 | 描述 | 备注 |
PROC_EXEC_ID | VARCHAR2(32) | 执行实例ID主键 | |
PROC_INST_ID | VARCHAR2(32) | 流程实例ID | |
PROC_STARTER_ID | VARCHAR2(100) | 流程发起人(只有父流程有此字段) | |
EXEC_STATUS | NUMBER | 流程实例状态 | 0:进行中,1:挂起,2:结束,3:强制终止。只有 |
PROC_NODE_ID | VARCHAR2(32) | 所属流程节点ID | |
EXEC_START_TIME | DATE | 执行实例开始时间 | |
EXEC_END_TIME | DATE | 执行实例结束时间 | |
KEY_WORDS | VARCHAR2(4000) | 实例名称,关键词 | |
BUSINESS_KEY | VARCHAR2(4000) | 关键字, | |
PARENT_ID | VARCHAR2(32) | 父执行实例ID,即:PROC_EXEC_ID | |
ELAPSED_TIME | NUMBER | 耗时(ms) | |
ACTIVE | NUMBER | 实例状态(流程实例、执行实例) |
说明:
- 流程实例与执行实例关联:当流程为直线型,那么此表就一条记录,
当涉及到多实例、或签、会签时,会产生一个流程实例,多个执行实例,此时流程实例与执行实例是一对多的关系。
- 当父流程执行实例强制终止时,下属的子流程执行实例均终止。
- ACTIVE:每当完成一个执行实例时,就会把ACTIVE置为0,非激活状态。当刚刚创建时,此字段值为1,激活状态。
- 当子流程执行实例进行时,父流程执行实例为非激活状态。
- 当每个子流程实例运行结束时,均需要判断是否达到激活父流实例的条件。激活条件是在父流程节点进行配置的。
- 会签会启动多个子流程执行实例,但是都归属同一个父流程执行实例。在每个子流程执行实例中,又是一个完整的流程执行实例生态体系。
- 耗时:每个流程实例均会记录耗时时间。
6.3.3 ES_PROC_TASK(流程任务):一个流程执行实例下的执行任务
字段 | 类型 | 描述 | 备注 |
PROC_TASK_ID | VARCHAR2(32) | 主键 | |
PROC_EXEC_ID | VARCHAR2(32) | 流程实例的主键 | |
PROC_INST_ID | VARCHAR2(32) | 流程实例ID | |
PROC_BASE_ID | VARCHAR2(32) | 所属流程定义ID | |
PROC_TASK_NAME | VARCHAR2(100) | 任务名 | |
ELAPSED_TIME | NUMBER | 耗时(ms) | |
TASK_START_TIME | DATE | 任务开始时间 | |
TASK_END_TIME | DATE | 任务结束时间 | |
OPERATOR | VARCHAR2(100) | 任务操作人 | |
TASK_STATUS | NUMBER | 任务状态 | 0:已创建 1:完成 2:作废 |
说明:
TASK_STAUS:当多实例中的某些任务完成后,会把其他相关的任务状态置为2
6.3.4 ES_PROC_VAR (流程任务变量):存储流程执行过程中流程实例及任务中设置的变量信息
字段 | 类型 | 描述 | 备注 |
PROC_VAR_ID | VARCHAR2(32) | 主键 | |
PROC_INST_ID | VARCHAR2(32) | 流程实例ID | |
PROC_EXEC_ID | VARCHAR2(32) | 流程执行实例ID | |
PROC_TASK_ID | VARCHAR2(32) | 流程任务ID | |
VAR_NAME | VARCHAR2(500) | 变量名称 | |
VAR_TYPE | VARCHAR2(10) | 变量类型 | 字符串:str 数值型:num 二进制:byte |
VAR_STR | VARCHAR2(4000) | 字符串 | |
VAR _NUM | NUMBER | 数值 | |
VAR _BYTE | BYTE | 二进制变量值 | |
CREATE_TIME | DATE | 创建时间 | |
MODIFY_TIME | DATE | 修改时间 |