1. 业务背景
项目开发过程中如果均是简单的数据请求与返回,那么方法调用和业务逻辑是最容易处理的,根据入参返回数据即可,数据的生命周期始于请求,终于数据返回,没有其他。
倘若特定需求场景需要多个接口协作完成一件事,数据流转存在多路由,业务逻辑处理将会呈现复杂化,朴素的数据流控制方式就是定义数据中间态通过硬编码形式来影响数据流向,这种设计在复杂度不深的情况下总是很容易实现,开发成本和沟通成本较低,也不失为一种非常有效的开发设计方式。
而随着业务场景复杂化,流程变更频繁,开发人员会在之前得益的简单设计上发现维护和可拓展性极差,甚至陷入流程泥潭中难以自救,最直接的表现就是接口交互定制化,所有的交互看不到任何业务或故事主线,所有的服务交互都需要最原始的那些开发人员的文档、注释甚至“言传身教”的指导才可以洞察复杂业务的其中一二,这是软件开发中的技术负债和不完善,我们急需一个可以引导完整业务流程的体系或者框架来引导服务交互,来驱动业务数据流转,对数据的出生、中转、停留及最终消亡进行有效控制和监管,让服务有源可溯,有序可遵。关于以上概述都是为了引申出下面项目实践的利器,工作流。
-
数据流转依赖硬编码,面向接口交互,没有统一司令塔服务进行调度
-
面向业务数据设计,复杂逻辑的业务中,数据流故事主线不清晰,无法监控也无从溯源
2.技术调研
JBPM vs Activiti选型对比
关于工作流开源框架,一般有JBPM和Activiti,简单检索了下两者对比如下:
Activiti持久层通过MyBatis实现、与Spring融合支持事务,与当前项目技术背景较为符合,且上手较为便捷,参考资料广泛,学习成本较低,加上之前个人项目运用过Activiti前身,对PVM设计模式有一定了解,最终决定采用Activiti作为工作流来进行开发。
Activiti工作流特点
关于Activiti工作流的具体内容这里不做赘述,本文的核心放在Activiti工作流与业务结合的实践,下面是Activiti工作流的一些特点:
-
数据持久化
-
支持链式API编程风格,所有的编程参与对象都可通过ProcessEngine获取到
-
支持流程设计器。可以结合IDEA中的ActiBpm等插件进行可视化流程设计,它最终转换的是bpm文件,是一个类xml的流程配置文件
-
原生支持Spring
-
分离运行时与历史数据
-
Activiti是基于单库单表的持久化
3.流程设计
目前负责的项目是一个关于用户认证相关的业务,简单描述认证业务流程如下:
-
工作流起始 工作流的开始和结束,是整个工作流程的起点和重点
-
工作流节点 工作流的核心节点和衔接,每个节点是故事主线的主要构成部分,代表一个聚合的业务逻辑,每个节点根据预定义走向进行数据驱动
-
工作流路由 工作流路由分发,根据前置数据来进行工作流走向的决策,从而影响后续节点的流转
-
数据中间态 除了开始、结束两个节点,被虚线包括的部分都是数据的中间态,无论业务数据和工作流数据,此时呈现的最大特点就是数据的不稳定性,业务处理中随时都可能根据外部业务驱动产生数据和业务流程的向下继续、向上回溯、分叉决策等,也是工作流中最活跃的部分
4.架构设计
Service业务逻辑层
-
Activiti Service 封装了工作流基础API,这里主要用到Activiti的
RepositoryService
、RuntimeService
、TaskService
三个服务类,RepositoryService
提供了服务流程部署功能,RuntimeService
提供了运行时服务这里主要涉及任务启动,TaskService提供了任务完成、任务取消等流程驱动的方法支持。这里是基于Activiti原生API进行了定制化任务支持,类似一个门面服务,主要是用来融合Activiti原生服务和实际项目业务需求的API -
WorkFlowAspect 通过Aop切面将工作流逻辑进行抽离,保证与业务层具体逻辑方法的隔离实现解耦;调用底层
Activiti Service
是提供的服务接口完成工作流驱动 -
@WorkFlowHandle 通过Spel解析
Annotation
注解形参,支持Map类型复杂数据结构传参,结合Aop进行实参映射绑定,解决切面层与业务层参数传递问题;通过@Repeatable
支持业务方法的重复注解,为工作流作用业务方法的灵活配置的可扩展提供支持
Dao数据持久层
-
水平拆分 根据业务数据的库表设计进行工作流持久化改造,按照用户userId进行工作流数据水平拆分
-
事务支持 由于切分键一致,使得工作流数据与业务数据分离的同时能够支持数据库事务,保证数据完整一致性,减少开发复杂度
5.项目实战
5.1 maven配置
目前Activiti已进入7.0.0+,翻阅了大量网上资料,该版本输出的时间较少,大部分还集中在5或6,这里基于可用可操作的思想选用了介绍参考资料详实的5.22.0进行开发,每个大版本变更差异较大,其他版本根据实践需要进行升级或取舍
<activiti.version>5.22.0</activiti.version>
<!-- activiti -->
<dependency>
<groupId>org.activiti</groupId>
<artifactId>activiti-engine</artifactId>
<version>${activiti.version}</version>
</dependency>
<dependency>
<groupId>org.activiti</groupId>
<artifactId>activiti-spring</artifactId>
<version>${activiti.version}</version>
</dependency>
5.2 持久化改造
分库分表
Activiti默认是单库单表的,而我们现有项目是分库分表的,路由策略是根据用户ID进行水平切分的。
对Activiti的持久化存储进行分库分表改造基于以下两点考虑:
-
业务数据分库分表,Activiti工作流数据的流转肯定是要和业务数据做联动的,按照同样语义即用户ID进行水平切换路由可以轻松实现数据库事务,减少实现数据一致性带来的设计问题。
-
业务表做了分库分表的原因就是用户体量大,持久化数据庞大,尽管读多写少且写入量单库单表在一段时间内不会对业务性能产生丝毫影响,但是为了扩展性更好,也为了规避后续变更难度,决定对Activiti工作流数据持久化也进行水平拆分。
字段及索引长度适配
为了保证数据完整性,Activiti默认创建表存在大量的外键约束,在生产环境下可以根据自身开发需要对这些外键进行去除,从而提高库表查询和处理性能。关于数据完整性可以通过应用程序层面进行保证和处理。
Activiti的库表设计都是基于可用性和普遍适配性进行设计的,个别字段如存在varchar(4000)
来记录异常信息,根据自身业务存储及性能考虑进行了部分字段基于可用性的缩减;还有部分表存在大量的联合索引,由于字段长度过长导致联合索引存储大于736字节,这是MySQL官方推荐的索引最大值,超过该值可能会产生性能问题,项目中没有对MySQL默认限制进行修改,而是根据项目情况缩减了联合索引字段的长度。
5.3 部署bpmn更新问题
在实际项目环境下,没有bpmn流程调整我们是不需要频繁进行bpmn流程部署的,每次新流程部署都会更新刷下流程ID、实例ID等,而且数据也会产生变更调整,而我们的需求是在需要更新的时候更新,不需要更新的时候复用即可。
<