乐趣区

关于开源:开源规则引擎ice致力于解决灵活繁复的硬编码问题

背景介绍

业务中是否写了大量的 if-else?是否受够了这些 if-else 还要常常变动?

业务中是否做了大量形象,发现新的业务场景还是用不上?

是否各种调研规定引擎,发现不是太重就是接入或保护太麻烦,最初发现还是不如硬编码?

接下来给大家介绍一款全新的开源规定引擎——ice,以一个简略的例子,从最底层的编排思维,论述 ice 与其余规定引擎的不同;讲述 ice 是如何应用全新的设计思维,符合解耦和复用的属性,还你最大的编排自由度。

规定引擎的利用场景

规定引擎在很多业务场景中都有利用,例如:

会员营销:由多种条件、流程、处分组合而成,工夫线简单,代码复用率不高,调整频繁。

风控规定:由多种条件组合并返回决策,条件量大且简单,变动频繁。

数据分析:将数据通过分析师本人编排的规定产出想要的数据,千人千面。

以上场景往往都存在一些独特痛点:

灵便业务(变动频繁,时效性显著,测试逻辑简单)

谋求灵便花里胡哨:产品和经营始终在摸索陈腐玩法,导致很多形象进去的模块往往扛不过两个迭代。

明天上线又要调整:因为一些偶发状况,如线上用户参与度不高,及时调整用户参加门槛等(当然也能够在开发前把所有状况思考到位,然而为了小概率事件做大量的工作,老本过高)。

研发测试心力交瘁:研发硬编码,测试验证简单反复逻辑,长此以往变的愈发疲乏。

工夫线(多条工夫线交错凌乱)

研发编排错了再来:个别营销类型的会波及很多工夫线,而在以后,测试一个将来要上线的具备不同工夫节点属性的流动,硬编码时往往由研发编排工夫,测试进行测试,然而当 bug 产生并打乱工夫线时,就须要从新编排工夫(没有经验过的不必太理解,前面会说)。

测试并行孔融让梨:当工夫线发生冲突并有多个测试在抵触地位上并发测试,往往由测试自行协调测试程序,当一方呈现问题往往导致后续测试进度不可控。

其余问题

依赖挂了难以为继:测试环境为非稳固环境,一旦依赖出了问题不免影响进度,如何能做到简略高效 mock?

修复数据苦不堪言:当线上问题产生时,受影响的客户如何疾速高效的弥补?

开源规定引擎 ice 的设计思路

为了不便了解,设计思路将随同着一个简略的充值例子开展。

举例

X 公司将在国庆放假期间,发展一个为期七天的充值小流动,流动内容如下:

流动工夫:(10.1-10.7)

流动内容:

充值 100 元 送 5 元余额 (10.1-10.7)

充值 50 元 送 10 积分 (10.5-10.7)

流动备注: 不叠加送(充值 100 元只能取得 5 元余额,不会叠加赠送 10 积分)

简略拆解一下,想要实现这个流动,咱们须要开发如下模块:

如上图,当用户充值胜利后,会产生对应充值场景的参数包裹 Pack(类 Activiti/Drools 的 Fact),包裹里会有充值用户的 uid,充值金额 cost,充值的工夫 requestTime 等信息。咱们能够通过定义的 key,拿到包裹中的值(相似 map.get(key))。

模块怎么设计无可非议,重点要讲的是前面的怎么编排实现配置自在,接下来将通过已有的上述节点,解说不同的规定引擎在外围的编排上的优缺点,并比拟 ice 是怎么做的。

流程图式实现

类 Activiti、Flowable 实现:

流程图式实现,应该是咱们最常想到的编排形式了~ 看起来十分的简洁易懂,通过非凡的设计,如去掉一些不必要的线,能够把 UI 做的更简洁一些。但因为有工夫属性,其实工夫也是一个规定条件,加上之后就变成了:

看起来也还好。

执行树式实现

类 Drool s 实现(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 就能够(叠加送与不叠加送的逻辑在这个节点上,属于这个节点的逻辑就该由这个节点去解决)。

对于 ③ 因为库存的有余,相当于没有给用户发放,则 AmountResul 返回 false,流程还会持续向下执行,不必做任何更改。

再加一个辣手的问题,当工夫线简单时,测试工作以及测试并发要怎么做?

一个 10.1 开始的流动,肯定是在 10.1 之前开发上线结束,比方我在 9.15 要怎么去测试一个 10.1 开始的流动?在 ice 中,只须要略微批改一下:

如图,引入一个负责更改工夫的节点 TimeChangeNone(更改包裹中的 requestTime),前面的节点执行都是依赖于包裹中的工夫即可,TimeChangeNone 相似于一个改工夫的插件一样,如果测试并行,那就给多个测试每人在本人负责的业务上加上改工夫插件即可。

ice 的个性

为什么这么拆解呢?为什么这样就能解决这些变动与问题呢?

其实,就是应用树形构造解耦,流程图式和执行树式实现在改变逻辑的时候,未免须要前怕狼; 后怕虎,然而 ice 不须要,ice 的业务逻辑都在本节点上,每一个节点都能够代表繁多逻辑,比方我改不叠加送变成叠加送这一逻辑就只限度在那个 ANY 节点逻辑上,只有把它改成我想要的逻辑即可,至于子节点有哪些,不必特地在意,节点之间依赖包裹流转,每个节点执行完的后续流程不须要本人指定。

因为本人执行完后的执行流程不再由本人掌控,就能够做到复用:

如图,参加流动这里用到的 TimeChangeNone,如果当初还有个 H5 页面须要做出现,不同的出现也与工夫相干,怎么办?只须要在出现流动这里应用同一个实例,更改其中一个,另一个也会被更新,防止了到处改工夫的问题。

同理,如果线上出了问题,比方 sendAmount 接口挂了,因为是 error 不会反回 false 继续执行,而是提供了可选策略,比方将 Pack 以及执行到了哪个节点落盘起来,等到接口修复,再持续丢进 ice 从新跑即可(因为落盘工夫是产生问题工夫,齐全不必放心流动完结了的修复不失效问题),同样的,如果是不要害的业务如头像服务挂了,然而仍然心愿跑起来,只是没有头像而已,这样能够抉择跳过谬误继续执行。这里的落盘等规定不细开展形容。同样的原理也能够用在 mock 上,只须要在 Pack 中减少须要 mock 的数据,就能够跑起来。

引入前置节点

下面的逻辑中能够看到有一些 AND 节点严密绑定的关系,为了视图与配置简化,减少了前置 (forward) 节点概念,当且仅以后置节点执行后果为非 false 时才会执行本节点,语义与 AND 相连的两个节点统一。

Talk is cheap. Show me the code…

github:https://github.com/zjn-zjn/ice

gitee:https://gitee.com/waitmoon/ice

欢送大家应用体验开源的规定 / 流程引擎 ice。如果有遇到问题,欢送提 issue 来交换。大家也能够增加作者微信:lwaitmoonl,备注“ice”,进入交换群。

Dev for Dev 专栏介绍

Dev for Dev(Developer for Developer)是声网 Agora 与 RTC 开发者社区独特发动的开发者互动翻新实际流动。透过工程师视角的技术分享、交换碰撞、我的项目共建等多种形式,汇聚开发者的力量,开掘和传递最具价值的技术内容和我的项目,全面开释技术的创造力。

退出移动版