网上关于工作流引擎有比较多的简介,也有很多工作流的实际应用场景。本文结合笔者多年对工作流的经验来阐述一下对工作流的理解。
一、什么是工作流?
先贴上wiki百科对于工作流的定义
工作流(Workflow),是对工作流程及其各操作步骤之间业务规则的抽象、概括描述。 工作流建模,即将工作流程中的工作如何前后组织在一起的逻辑和规则,在计算机中以恰当的模型表達并对其实施计算。 工作流要解决的主要问题是:为实现某个业务目标,利用计算机在多个参与者之间按某种预定规则自动传递文档、信息或者任务。
那么再简单点说,我认为工作流就是对业务的流程化抽象。WFMC给出了工作流参考模型如下:
为什么称之为“流”,则是各个节点通过内外部驱动触发引起节点的推进,形成一个流式的状态达到业务终点。比如一次用户查看淘宝商品的费用、一次支付成功后的权益开通、一次用户注册、一次调度任务的运行等,都是可以是一个工作流。
从代码层面上来说,工作流是对业务逻辑代码的按照指定的流程格式化。即原来可以用代码直接完成的任务流程,借助工作流工具来进行标准格式化、视图化。
另外要提一点,工作流本身是一种工程化的设计思想,在特定场景下,也是一种业务的实现方式。对于狭义的通用工程来说,工作流只是一种设计模式,或者说思维方式,不涉及任何的具体编码,即所有业务代码还是需要人工完成,只是用工作流的方式来规划和编排代码运行方式。而对于某些垂直的业务,工作流本身就是业务实现的具体方式,比如审批流的配置,可以直接通过工作流引擎的方式,直接实现配置化编排业务。
现在业务都趋于融合,所以很多工作流提供了默认的实现,也提供了自定义的实现。比如阿里巴巴旗下的业务【宜搭】就宣称“0代码搭建应用”。宜搭本身就是一种用工作流的思维提供的应用搭建平台。
二、什么是工作流引擎
1、工作流引擎
我们明白为什么需要工作流以及什么是工作流的定义。那么工作流引擎则是实现驱动工作流的一套实现工具。工作流本质上是业务流的抽象,因此不同分类的业务流则形成了不同的工作流,进而有不同的工作流引擎来负责对不同类别的工作流进行具体的定义和实现。
工作流设计器,我们常说的工作流引擎,一般都包括设计器的能力,即负责对业务流程的拖拽式工具, 有插件式也有WEB云端式的。
比如我们上述的审批流,则是一种特殊的工作流,该业务流程的特点,就是需要较多的人工介入参与同意/拒绝的操作。
因此实现审批流的引擎提供的图形化配置能力,并且具备和组织用户直接关联的功能及调用外部服务的能力。
上面三张分别是一个工作流引擎的插件视图、XML源码和编译生成的运行代码,可以看到通引擎,可以把视图解析成一套简单的可执行代码框架,帮助编排我们自己实现所需要的业务BEAN。
通用型的工作流引擎,比较知名的有jBPM5与Activiti,阿里巴巴集团则使用tbbpm多一些。下面是国外对于常用的开源的工作流引擎的一个对比。
注1:对比数据来自:https://workflowengine.io/blog/java-workflow-engines-comparison/
2、tbbpm和activiti对比
市面上比较知名的开源工作流引擎有jbpm和acitivi。
三、为什么需要工作流
相信大家都了解流水线的好处,蒸汽时代就是将手工化转变成了流水线化,手工有明显的一个缺点,就是生产效率极低,而流水线则可以提高生成效率。
那么工作流则是将业务流水线化,从原来的一团处理逻辑清晰的划分成为若干个步骤,每个步骤流转清晰明显。每个模块有非常高的内聚,模块之前有非常清晰的依赖。
外网最高的工作流的优点回答如下:
Workflows can help streamline and automate repeatable business tasks, minimizing room for errors and increasing overall efficiency. This, in turn, dramatically improves your business. Managers can make quicker, smarter decisions and employees are empowered to collaborate in a more productive and agile way.
举个例子,退款本身非常复杂,运营、产品、技术、财务可能都无法从单一的角色来解释清楚到底退款的整个链路和关键环节,但是通过工作流的方式来呈现,则所有人能快速看到退款到底是个什么样的业务。
根据实际业务中对工作流的大量使用,我们总结出工作流有以下的优点:
1、业务可视化
首先,最大的优点,就是可以借助工作流引擎,让业务可视化,可以通过视图看到整个业务流程,每个节点执行什么业务逻辑一目了然,分支处理、异常处理也非常清晰。
如上述工作流,你一眼就看到,退款成功后该处理哪些事情。业务懂了,自然写代码也就更快了。
2、业务可编排
如果业务永远不变化,那么我们硬编码在一个方法里也无所谓。但是我们知道业务千变万化的,软件设计很重要的一个指标就是灵活可扩展。工作流流程的重编排,则可以使得业务进一步在代码层面增加灵活性。可以通过节点的调整来快速调整业务流程,可以灵活增删节点,而不至于对整个流程有影响。
还是以上面的为例,如果要增加一个【关闭用户权益】的节点,或者删除【用户消息】,那么我们很容易利用工作流增删原有流程。这里实现了代码可维护里最核心的两点:
改动代码最简单和改动代码最快。
3、自动重试
对于某些工作流来说,工作流引擎提供了框架层面持久化和自动重试的能力。
上述是实际交易生产系统接收支付宝支付成功后回调后要处理的异步流程。而且在业务变化时,还需要对流程里动态增删业务功能。由于像【优惠券处理】或者【活动处理】节点依赖条件较多,很可能会处理失败需要重试,但是又不想把所有节点重试一遍,此时工作流引擎的持久化和节点重试能力,则是非常完美的方案。
四、工作流使用场景
那么适合用工作流的业务有什么特点?
1、领域业务高复杂度
对于偏向业务系统的逻辑,并且具备一定的领域专业性,比如进销存、CRM、订单管理等具备一定的领域复杂度的业务,可以用工作流模式,来实现业务的可视化。从全局的业务视角来观察整体系统架构,而不至于在代码大山面前无从下手。
2、多节点、长链路
比如询价需要经过加载用户信息、加载商品、加载优惠、计费等多个节点,每个节点都相对独立。此类业务就比较适合用无状态的内存工作流。
3、状态持久化和自动重试
对于异步的调度流程,例如订单支付成功后,驱动下游业务系统开通、发送用户提醒消息、扣减库存等异步流程节点,需要持久化每个节点的执行状态,同时在流程失败的情况下系统框架能进行重试恢复。
五、工作流分类
那么工作流该如何分类,如何抽象和归类自身的业务?这是一个首要问题,从经验上来看,我们把工作流按照业务特性分成了以下几类:
1、内存工作流
内存工作流是最近简单的一类工作流,该类工作流无需持久化,无需状态,在内存调用完成即可。这种工作流也表示业务本身无状态、无需持久化的。比如用户的一次询价行为,用户到详情页查看价格和库存,对于后端来说是一个非常复杂的调用流程,因此会把这一次调用抽象成工作流。
此类流程用硬编码的方式也能很好的实现,但是用工作流的方式,则会使得业务可视化,从视图上就能快速的看出业务是如何运转的,有哪些分支等。同时也使的每个节点能够最大程度的复用。
2、状态机工作流
当我们需要记录每个节点是否执行成功,并且具备系统自动重试的时候,我们就需要状态机工作流。状态机是在内存工作流的基础上,增加每个节点状态持久化,并增加重试机制。
我们以一个工作流为例,状态机工作流的一个实例,即对应一个业务上产的流程实例,对应在market_bpm_statemachine_instance的一条记录。
在一条实例里,记录当前处理节点[current_node]、当前处理状态[status]、当前节点重试次数[retry_times]、当前节点处理异常信息[error_message]以及业务主键ID[biz_id]。
当某个节点处理失败后,节点置为异常状态,工作流调度模块会捞取失败的节点继续按照工作流预定义的流程重试, 直到重试到指定的配置次数后,将整个流程置为执行失败,此时需要人工介入。
另外,一般的工作流引擎提供了默认集成的调度框架,我们也可以自己采用第三方的调度工具,只要使用IStateMachineQueryService框架内置的接口查询出待执行的数据即可。
3、人工工作流
人工工作流,又可以认为是外部触发驱动工作流,至少是存在一个或者多个节点是待外部确认才能推进整体业务流程。
和状态机相比,人工工作流多了外部触发的功能。为某一些需要外部触发的业务提供了很好的支持,比如业务流程依赖审批的,比如退款流程,需要负责人审批通过后才能进行打款;比如报销打款,需要财务审批成功后,才能对报销单进行打款核销;还有一些物流流程,用户确认到货后,驱动流程继续往下走,比如提供售后服务、关闭物流单等。
以工作流为例,人工工作流的设计会稍微复杂一些,分了多个表,每个表我简介一下其意义
bpm_workflow_instance : 工作流实例表,表示一个具体执行的业务流 bpm_task_instance : 任务实例,将工作流的每个节点当做一个任务实例存储下来,描述一个工作流实例里 每个节点的具体状态 bpm_param_instance : 参数实例,工作流或者任务实例的上下文入参快照 bpm_timer_task:处理定时任务表,比如人工节点未审批自动超期等 bpm_sequence :生成上述四个表的主键ID表
可以看到和状态机相比,人工工作流多了对每个任务的流式快照以及定时功能,主要是解决人工等外部触发的自动、超期等问题。
六、工作流设计准则
我们可以认为,对于复杂流式业务,面向工作流编程是一个思路,也是一个基本设计理念和方向。如何面向工作流设计?如何让工作流更好的辅助业务编排和业务推进,则是一个需要认真思考的话题。
1、流程抽象
首要的工作,就是要把业务抽象流程化。即将业务用精简的流程图的方式展现出来。对业务的深刻理解,才能抽象出业务流程,包括业务链路、关键节点和分支、异常和重试逻辑、是否需要持久化、是否需要外部输入信息。
这个环节实际上就是领域驱动设计里的,成为领域专家或者和领域业务专家对焦业务的重要部分,即深入业务,读出业务真正的核心流程、异常流程、分支流程以及容错流程。
这里需要对业务流程多问几个问题,比如询价流程里,商品加载不到怎么处理?退款流程里等待ISV确认同意退款,如果ISV一直没有同意怎么办?报销流程里,审批单提交了财务人员离职了怎么办?这些问题都思考清楚了,对整体业务流程也就有了一个全面的认识,才能清楚的定义真正的业务流程和业务流程闭环。
另外,根据业务类型,选择是否需要持久化节点,比如用户询价、查看商品详情等只读业务,则直接采用内存式工作流;如果有持久化重试需求,比如处理支付回调消息,驱动开通用户权益,当流程处理失败,必须保存当前的流程快照并且需要进行重试补偿,则可以选用状态机工作流。当需要有外部用户触发,例如用户审批等人工节点参与,则可以使用人工工作流。
画出最优的业务流程图,是工作流设计中的首要,也是最重要的一个步骤。
2、流程编排
完成业务流程产出后,就要利用工作流引擎工具提供的功能,对业务流进行具体的实现,即“流程编排”。流程编排包括几部分:画出工作流图、工作流配置等。
如上述的退款业务,首先需要将业务流程化编排,抽象出核心的关键节点,比如【退订判断】,判断是否符合退款条件,其次【更新订单】将订单更新为退款申请中,给用户一个良好的体验,然后将【订购置为退款中】,防止退款期间系统产生新的订单,然后【汇金申请退订】向底层交易发起退款申请,根据底层返回成功和失败分别做【订单订购恢复】的回滚操作以及【保存退款申请】的结束动作。
编排需要判断哪些是自动节点,哪些需要用判断节点,调用失败如何处理等。就理论上而言,每个节点都可能执行失败,即每个节点都可以做成判断节点,但是这样的缺点很明显,主要链路不清晰,分支非常多,即大家会额外的去关注节点失败的分支,尽管实际上可能无需单独关注异常分支。
如上图所示,同样的一个流程,可以有两种编排的方法,进而也导致了接口有着不同的设计。那么到底如何编排节点是最优的?有个约定的标准:
以业务流程判断为分支节点,屏蔽系统或非预期的业务异常分支,系统或非预期的业务异常在工作流框架外统一处理
即以上述退款为例,调用订单更新在正常情况下理论上应该一定是成功的,因此无需设计成判断节点,可以直接设置成自动节点,当该接口发生不在预期的异常时,可以直接throw系统异常,直接在节点外进行捕获处理。具体参考“异常处理”步骤的推荐做法。
3、接口设计
面向工作流的接口设计原则,首先满足接口本身的设计原则“高内聚低耦合”。为了满足节点工作流的复用性,接口尽量要保持基本入参,而不要使用复杂对象或者弱类型的MAP入参。
/** * 订单置为退款中(一个不好的设计,中间的一个节点,入参采用了复杂上下文请求ProdRefundApplyRequest,其他业务无法复用) * @return */
ServiceResult<Boolean> updateOrderRefunding(ProdRefundApplyRequest prodRefundApplyRequest);
上述接口则抽象度不够,导致调用困难(组装ProdRefundApplyRequest上下文复杂),复用困难(其他业务可能不包含ProdRefundApplyRequest内的字段或者属性)。
/** * isv 确认(一个好的设计,中间的一个节点,入参采用了基本的入参,其他节点和业务可以快速复用) *
* @param mainOrderId 退款主订单
* @param opUid
操作人uid
* @param orgId 操作人的组织id
* @return * @throws ServiceException
*/
Boolean confirmByProvider(Long mainOrderId, Long orgId, Long opUid) throws ServiceException;
4、异常处理
如何统一处理工作流中的异常?一般的工作流引擎工具中就设计了对异常的统一处理方式。但是还是需要接口设计本身对异常加以处理,避免对于异常的处理不合理,导致流程难以理解和捕捉。
上图就是一个较为通用的处理办法,工作流本身不处理业务异常,业务异常统一上抛至应用层处理。另外捕获Exception,防止工作流本身产生的任何异常(如果工作流已经处理,则可以忽略)。
统一处理异常的优点是可以统一工作流节点错误实现方式,还可以打印错误提醒日志,方便快速定位工作流运行期间产生的异常问题。
七、总结与思考
工作流提供了一种很好的工程化的方式来解决业务问题,使得业务抽象、流程格式化、易维护和易拓展,一定程度的业务可视化。
从工程上来说,通用工作流即是一块可同步执行或异步执行的代码块上,增加了格式化和视图化。
当然,工作流设计模式也不是放之四海皆准。引入工作流本身会增加工程难度,特别是目前没有一个非常极致体验的工作流引擎的情况下。就笔者来说,常用的tbbpm就存在较多的问题,比如图形拖拽不美观、上下文变量设置麻烦、容易出异常未知问题。另外对于一些简单的业务逻辑,几段代码搞定的,也无需考虑使用工作流。
工作流用的好的话,也是对整体业务思维逻辑的体现。工作流能划清楚,至少业务已经懂了,剩下的只是撸码问题,而撸码是最简单的。就好比一个算法题,设计算法是最难的,具体代码实现则降低了一个数量级,当然,编程功底不过关的另说。
工作流用得好,从另外一个角度来说,业务可维护、业务可视化,对后来者学习和维护的成本都很低。基本上来说,用工作流设计的逻辑,很少出现翔一样的不可维护,想打人的代码。
工作流用的好,整个流程具备闭环,具有很强的容错性。异常分支的考虑、错误节点的重试、异常节点告警、节点持久化、超时节点配置,系统就具备很强的鲁棒性了。
工作流用的好,当然,下班就会更早。
总而言之,只要符合复杂领域多节点长链路的业务,都可以采用工作流的方式,至少,可以使用工作流的思考方式。
关注我,交流更多工作流