ice简介
致力于解决灵活繁复的硬编码问题
官方文档:http://waitmoon.com/docs
背景介绍
规则/流程引擎想必大家也都并不陌生,耳熟能详的就有Drools,Esper,Activiti,Flowable等,很多大厂也热衷于研究自己的规则引擎,都是用于解决灵活场景下的复杂规则与流程问题,想要做到改改配置就可以生成/生效新的规则,脱离硬编码的苦海。毕竟改改配置和在已有基础上编排规则/流程,比硬编码的成本低很多,但是使用市面上现有的规则引擎来编排,一来接入成本和学习成本都不低,二来随着时间的推移,规则变的越发庞大以及一些场景的不适用,更加让人叫苦不迭
场景
**会员营销:**由多种条件,流程,奖励组合而成,时间线复杂,代码复用率不高,调整频繁
**风控规则:**由多种条件组合并返回决策,条件量大且复杂,变动频繁
**数据分析:**将数据通过分析师自己编排的规则产出想要的数据,千人千面
以上场景往往都有一些共性:
灵活业务
追求灵活花里胡哨:产品和运营一直在探索新鲜玩法,导致很多抽象出来的模块往往抗不过两个迭代
今天上线又要调整:因为一些偶发情况,如线上用户参与度不高,及时调整用户参与门槛等(当然也可以在开发前把所有情况考虑到位,但是为了小概率事件做大量的工作,成本过高)
研发测试心力交瘁:研发硬编码,测试测复杂重复逻辑,久而久之变的愈发疲惫
时间线
研发编排错了再来:一般营销类型的会涉及很多时间线,而在当前,测试一个未来要上线的具有不同时间节点属性的活动,硬编码时往往由研发编排时间,测试进行测试,但是当bug发生并打乱时间线时,就需要重新编排时间(没有经历过的不用太了解,后面会说)
测试并行孔融让梨:当时间线发生冲突并有多个测试在冲突位置上并发测试,往往由测试自行协调测试顺序,当一方出现问题往往导致后续测试进度不可控
其他问题
依赖挂了难以为继:测试环境为非稳定环境,一旦依赖出了问题难免影响进度,如何能做到简单高效mock?
修复数据苦不堪言:当线上问题产生时,受影响的客户如何快速高效的补偿?
设计思路
为了方便理解,设计思路将伴随着一个简单的充值例子展开
举例
X公司将在国庆放假期间,开展一个为期七天的充值小活动,活动内容如下:
活动时间:(10.1-10.7)
活动内容:
充值100元 送5元余额 (10.1-10.7)
充值50元 送10积分 (10.5-10.7)
**活动备注:**不叠加送(充值100元只能获得5元余额,不会叠加赠送10积分)
简单拆解一下,想要完成这个活动,我们需要开发如下模块:

图中发现有待发放key,这个key是从哪里来呢:
如图,当用户充值成功后,会产生对应充值场景的参数包裹Pack(类Activiti/Drools的Fact),包裹里会有充值用户的uid,充值金额spend,充值的时间requestTime等信息。我们可以通过定义的key,拿到包裹中的值(类似map.get(key))
模块怎么设计无可厚非,重点要讲的是后面的怎么编排实现配置自由,接下来将通过已有的上述节点,讲解不同的规则引擎在核心的编排上的优缺点,并比较ice是怎么做的。
流程图式实现
类Activiti、 Flowable实现
流程图式实现,应该是我们最常想到的编排方式了~ 看起来非常的简洁易懂,通过特殊的设计,如去掉一些不必要的线,可以把UI做的更简洁一些。但由于有时间属性,其实时间也是一个规则条件,加上之后就变成了:
看起来也还好
执行树式实现
类Drools实现(When X Then Y)
这个看起来也还好,再加上时间线试试:
依旧比较简洁,至少比较流程图式,我会比较愿意修改这个。
变动
上面两种方案的优点在于,可以把一些零散的配置结合业务很好的管理了起来,对配置的小修小改,都是信手拈来,但是真实的业务场景,可能还是要锤爆你,有了灵活的变动,一切都不一样了。
理想
不会变的,放心吧,就这样,上
现实
①充值100元改成80吧,10积分变20积分吧,时间改成10.8号结束吧(微微一笑,毕竟我费了这么大劲,终于提现到价值了!)
②用户参与积极性不高啊,去掉不叠加送吧,都送(稍加思索,费几个脑细胞挪一挪还是可以的,怎么也比改代码再上线强吧!)
③5元余额不能送太多,设置个库存100个吧,对了,库存不足了充100元还是得送10积分的哈(*卒…*早知道还不如硬编码了)
以上变动其实并非看起来不切实际,毕竟小编我遇到的变动比这离谱的多的是,流程图式和执行树式实现的主要缺点在于,牵一发而动全身,如果考虑不到位,很容易被反噬,而且这还只是一个简单的例子,现实的活动内容要比这复杂的多的多,时间线也是很多条,考虑到这,再加上使用学习框架的成本,往往得不偿失,到头来发现还不如硬编码。
怎么办?
ice是怎么做的?
引入关系节点
关系节点为了控制业务流转
AND
所有子节点中,有一个返回false 该节点也将是false,全部是true才是true,在执行到false的地方终止执行,类似于Java的&&
ANY
所有子节点中,有一个返回true 该节点也将是true,全部false则false,在执行到true的地方终止执行,类似于Java的||
ALL
所有子节点都会执行,有任意一个返回true该节点也是true,没有true有一个节点是false则false,没有true也没有false则返回none,所有子节点执行完毕终止
NONE
所有子节点都会执行,无论子节点返回什么,都返回none
TRUE
所有子节点都会执行,无论子节点返回什么,都返回true,没有子节点也返回true(其他没有子节点返回none)
引入叶子节点
叶子节点为真正处理的节点
Flow
一些条件与规则节点,如例子中的ScoreFlow
Result
一些结果性质的节点,如例子中的AmountResult,PointResult
None
一些不干预流程的动作,如装配工作等,如下文会介绍到的TimeChangeNone
有了以上节点,我们要怎么组装呢?
如图,使用树形结构(对传统树做了镜像和旋转),执行顺序还是类似于中序遍历,从root执行,root是个关系节点,从上到下执行子节点,若用户充值金额是70元,执行流程:
ScoreFlow-100:false
AND:false
ScoreFlow-50:true
PointResult:true
AND:true
ANY:true
这个时候可以看到,之前需要剥离出的时间,已经可以融合到各个节点上了,把时间配置还给节点,如果没到执行时间,如发放积分的节点10.5日之后才生效,那么在10.5之前,可以理解为这条调用链不存在(可以理解为这个节点还没有上班,父节点不上班,绑在此父节点下面的逻辑也都不上班)
变动与问题的解决
对于①直接修改节点配置就可以
对于②直接把root节点的ANY改成ALL就可以(叠加送与不叠加送的逻辑在这个节点上,属于这个节点的逻辑就该由这个节点去解决)
对于③由于库存的不足,相当于没有给用户发放,则AmountResult返回false,流程还会继续向下执行,不用做任何更改
再加一个棘手的问题,当时间线复杂时,测试工作以及测试并发要怎么做?
一个10.1开始的活动,一定是在10.1之前开发上线完毕,比如我在9.15要怎么去测试一个10.1开始的活动?在ice中,只需要稍微修改一下:
如图,引入一个负责更改时间的节点TimeChangeNone(更改包裹中的requestTime),后面的节点执行都是依赖于包裹中的时间即可,TimeChangeNone类似于一个改时间的插件一样,如果测试并行,那就给多个测试每人在自己负责的业务上加上改时间插件即可。
特性
为什么这么拆解呢?为什么这样就能解决这些变动与问题呢?
其实,就是解耦,流程图式和执行树式实现在改动逻辑的时候,不免需要瞻前顾后,但是ice不需要,ice的业务逻辑都在本节点上,每一个节点都可以代表单一逻辑,比如我改不叠加送变成叠加送这一逻辑就只限制在那个ANY节点逻辑上,只要把它改成我想要的逻辑即可,至于子节点有哪些,不用特别在意,节点之间依赖包裹流转,每个节点执行完的后续流程不需要自己指定。
因为自己执行完后的执行流程不再由自己掌控,就可以做到复用:
如图,参与活动这里用到的TimeChangeNone,如果现在还有个H5页面需要做呈现,不同的呈现也与时间相关,怎么办?只需要在呈现活动这里使用同一个实例,更改其中一个,另一个也会被更新,避免了到处改时间的问题。
同理,如果线上出了问题,比如sendAmount接口挂了,由于是error不会反回false继续执行,而是提供了可选策略,比如将Pack以及执行到了哪个节点落盘起来,等到接口修复,再继续丢进ice重新跑即可(由于落盘时间是发生问题时间,完全不用担心活动结束了的修复不生效问题),同样的,如果是不关键的业务如头像服务挂了,但是依然希望跑起来,只是没有头像而已,这样可以选择跳过错误继续执行。这里的落盘等规则不细展开描述。同样的原理也可以用在mock上,只需要在Pack中增加需要mock的数据,就可以跑起来。
引入前置节点
上面的逻辑中可以看到有一些AND节点紧密绑定的关系,为了视图与配置简化,增加了前置(forward)节点概念,当且仅当前置节点执行结果为非false时才会执行本节点,语义与AND相连的两个节点一致
还有很多可以做的事情在这里不在赘述,如果看到这里并且还能理解的话,应该都会有一些想法(欢迎交流,哈哈哈)。
现行设计
理论有了,那就开整吧(本篇主要阐述思想,设计讲解后续更新,先放两张核心的结构图)
节点类图
框架中核心类关系图:
执行流程
请求处理和配置的拉取与更新过程:
Code
Talk is cheap. Show me the code…
https://github.com/zjn-zjn/ice
还有很多细节功能和设计没有写出来,后续会考虑持续更新~
受限于作者能力问题,代码暂时只能写成这样了,哈哈哈,也会不断的持续优化中~
有更好想法或者更多应用场景或者想一起探讨的小伙伴~ 欢迎交流(QQ群:587368939)~~