大家好,我是Chen~
它是一个比较老的Java规则引擎框架,我刚开始工作的时候是十几年前在一家第三方支付公司工作,记得是做核心支付路由层的。
难能可贵的是,该项目历经十余年依然保持开源和更新。
https://github.com/kiegroup/drools
它也是2020年开源的一个Java规则引擎,经过两年的迭代,现在它的功能和特性已经非常优秀,非常适合在高度复杂的核心业务中使用,同时还能保持业务的灵活性。
https://gitee.com/dromara/liteFlow
在本文中,我们将深入比较这两个框架,它们适合什么场景,有哪些相同和不同之处,以及它们在相同场景中的表现如何。
(基于7.6.0版本和2.9.0版本)
虽然作为该开源项目的作者,我这几天也深入研究过,力图从非常客观的角度去分析,很多对比结果都是基于实际使用后的感受,但题目作者难免会有一些主观心理和片面的理解,尤其是现在已经更新到8.X了。
规则引擎的定义
首先我想先明确一下规则引擎的定义,因为很多人容易混淆规则引擎和流程引擎的概念。
规则引擎通常嵌入在应用程序组件中,它将业务决策与应用程序代码分离,使用预定义的语义模块来编写业务决策,接受数据输入、解释业务规则并根据业务规则做出业务决策。
简单来说规则引擎主要解决逻辑易变、业务耦合的问题,规则驱动逻辑,以前硬编码在项目代码里的逻辑,可以用规则引擎带出来,随时热改。
流程引擎使多个业务参与者之间的交易能够按照某些预定义的规则流动,这通常涉及角色信息。
简单来说,流程引擎主要解决不同角色之间的业务流转问题,比如请假流程、审批流程等,往往要经过多个角色,通过规则驱动角色流转。
两个框架之间的异同
两者都是优秀的开源框架,能够将业务中的逻辑分离出来,并且都有自己的表达语法。
但不同的是,它更强调逻辑片段的规范化,可以将核心可变部分写进一个规则文件,相当于把原本用 Java 编写的代码搬到规则文件中,规则文件内的所有代码都可以热更改。
它基于组件的理念进行设计,强调组件的规范化,覆盖全业务。编排的最小单位是组件,规则文件用于衔接组件之间的流程。同时支持碎片化代码的规范化,因为还支持业务逻辑的脚本化。规则支持热变更。
因此判断一个规则引擎是否合格的主要因素有:
是否有灵活的正则表达式来支持
规则和Java能很方便的联系起来吗?
API调用是否方便?与各场景系统如何对接?
侵入式耦合比较
规则的学习成本以及是否容易上手
正则表达式有语言插件吗?
规则是否可以与业务松散耦合并存储在其他地方?
规则的变化能实时改变逻辑吗?
是否有支持非技术用户的界面?
框架性能
让我们从这些方面详细比较一下两个框架的性能。
常用表达
正则表达式是针对Java量身定制的,基于RETE算法规则引擎实现。
java的正则表达式比较接近自然编程语言,并且有自己的扩展文件drl,支持所有语法。drl基本上具备了自然编程语言的所有语法。因此,在drl文件中编写java逻辑是完全可能的。
我们来看看drl文件是什么样的:
drl文件
可以看到,规则定义的方式是,一个规则段中必须有一个明确的when...then,表示当满足某些条件时,该做什么。关注公众号:码猿技术专栏,回复关键字:1111,获取阿里巴巴内部Java性能优化手册!当触发规则时,会自动判断执行规则的哪一段。如果满足多个条件,则可以触发多条规则。
编排表达式简洁易懂,底层采用EL表达式语言封装,用于组件的流转,支持异步、选择、条件、循环、嵌套等场景。
组件级别不仅可以是Java组件,也可以是脚本语言,目前支持两种脚本语言,凡是可以用Java实现的东西,用脚本语言也可以实现。
规则文件如下所示:
规则文件
上面的排列表达式表达了以下逻辑流程:
的排列表达式
编排表达式支持THEN(同步)、WHEN(异步)、(选择)、IF(条件)、FOR(循环次数)、(条件循环)等大表达式,每个表达式都有许多扩展关键字可供选择。
脚本组件支持的基本语法与 Java 类似。您可以使用该语言支持的所有内容。您甚至可以在语法中定义其他类和方法。
“综上所述”
总体来说,两个框架都可以使用脚本来定义逻辑片段,在定义逻辑片段的层面上,它们使用了自研的语法和插件式的风格,其实我觉得更接近Java的语法,甚至可以在里面定义类和方法,在高级应用中,也可以使用规则来定义方法,但我觉得没那么自然。
最大的特点是除了定义逻辑片段之外,还可以编排全局组件。这也是编排规则引擎名字的由来。使用简单的编排语法,可以设计复杂的逻辑流程。支持混合Java和脚本。
与Java进行数据交换
在规则中可以使用关键字引入一些Java类包,以供调用。
在脚本组件中也可以通过导入任意java包来调用。
在函数中,可以直接引用事实对象。
在这个过程中,对象可以被直接引用,上下文贯穿整个编排链。
在 中,甚至可以在上下文中导入Bean,直接通过@注解来调用,利用这个特性,甚至可以在脚本中调用RPC,调用数据库dao对象来获取数据,虽然在 中也可以这样做,但是麻烦很多。
“综上所述”
它基本可以满足与Java的数据交换需求,但是显然它支持的场景更多。
API 和集成
在API调用层面,需要定义一系列的对象,框架只需要使用对象即可。
它支持编程访问,但是您需要编写大量配置类来集成它。
它不仅支持编程式的访问,还提供了环境中的自动组装访问方式,甚至不需要定义,可以直接从上下文中获取自动组装的对象进行调用。
综上所述
API更简单,集成度更高。
侵入式耦合比较
需要使用对象来匹配规则,并在需要规则的Java代码中调用它们。规则和Java是分离的。调用对象在调用层面是耦合的。
规则也与 Java 分离,但多了一个组件的概念,所以在组件层面需要继承,但也提供了声明式组件的选择。使用声明式方式时耦合度相对降低。在调用层面也需要调用对象。
“综上所述”
从耦合度上来说,API由于其编排特性,耦合度相对较高。
学习规则的成本
规则学习成本比较高,因为是自研的规则语法,所以需要很全面的熟悉过程,而且文档都是英文的。
编排规则极其简单,如果不用脚本组件的话,基本10分钟就能上手,即使用脚本,因为跟Java很像,学习成本也很低,而且有很多学习资料可以参考。
中英文文档齐全,并且还有良好的中文社区来解答问题。
综上所述
从规则学习成本来看,的规则学习曲线远高于的规则学习曲线。
有语言插件吗?
PHP 和 IDEA 都有插件可以进行语法高亮、预检查和提示。
IDEA 上有高亮、预检和提示插件,而 上没有。
综上所述
考虑到使用的人很少,基本上两个规则引擎都在语言插件方面实现了这一点。
规则的存储
理论上,你的规则可以存储在任何地方,但你需要手动操作。你可以自己存储和检索它们。
还有一个插件可以存储规则。只有这个不需要您自己访问。
除了本地规则外,它原生支持将规则存储到任意标准SQL数据库中,也原生支持Etcd等注册中心,你只需要进行配置即可。另外它还提供了扩展接口,方便你将其扩展成任意存储点。
“综上所述”
规则存储支持比 丰富得多。
规则的变化能实时改变逻辑吗?
热刷新规则的方式现在看起来有些傻,它的规则是通过jar来生成的,然后系统远程动态读取jar包来完成规则刷新。
而且定期的热修改也必须通过此方法进行。
这个层次就高级多了,如果你用 Etcd 等来存储数据,你什么都不用做,变更会自动刷新。如果你用 SQL 数据库存储或者本地存储,变更规则后,需要调用框架提供的 API 来进行热变更。两种方式都可以热更新,而且高并发下也比较流畅。
“综上所述”
在热更新设计方面更加先进。
有没有接口形式来支持它?
是的,它是一个独立的插件包,提供了编写规则和事实对象的 Web 界面,也提供了检查和部署的能力。但由于它主要关注逻辑片段,所以不需要提供编排层面的拖拽式 UI 功能,而只提供界面上编写规则的能力。
没有接口形式,目前只能通过第三方Etcd提供的接口完成接口规则的修改。
“综上所述”
在UI形态生态上领先一步。
框架性能
这里我们使用和实现相同逻辑的Demo。
根据订单金额加积分的演示示例。
案例逻辑很简单,根据订单金额动态确定加多少积分:
金额未满100元不增加积分。
100元至500元,加100积分。
500至1000元,另加500积分。
消费满1000元以上,可额外获得1000积分。
规则如下:
package rules; import com.example.droolsdemo.entity.Order; rule "score_1" when $order:Order(amount<100) then $order.setScore(0); System.out.println("触发了规则1"); end rule "score_2" when $order:Order(amount>=100 && amount < 500) then $order.setScore(100); System.out.println("触发了规则2"); end rule "score_3" when $order:Order(amount>=500 && amount < 1000) then $order.setScore(500); System.out.println("触发了规则3"); end rule "score_4" when $order:Order(amount>=1000) then $order.setScore(1000); System.out.println("触发了规则4"); end
等效规则如下:
< 100){ return "a"; }else if(amount >= 100 && amount < 500){ return "b"; }else if(amount >= 500 && amount < 1000){ return "c"; }else{ return "d"; } ]]> SWITCH(w).TO(a, b, c, d);
当两个框架都是全脚本编写时,测试时删除所有打印日志,执行10万次,结果如下:
执行10万次,耗时0.7秒
整个脚本组件执行了 100,000 次,耗时 3.6 秒
由于在全脚本组件的情况下,需要执行脚本并且需要安排脚本的执行,因此耗时较长。
如果把该组件替换成Java然后执行,结果如下:
所有 Java 组件执行 100,000 次,耗时 0.5 秒
综上所述
如果以完整脚本模式运行它,则需要的时间将超过 10。如果以完整 Java 组件模式运行它,其性能会稍微好一些。
所以,如果您想要更高的性能,请使用 java 组件,如果您想要更高的灵活性,请使用脚本组件。
其实在实际业务中,把容易变动的逻辑提取出来写入脚本组件,采用Java+脚本混合的方式是更加推荐的做法。
结论
为什么拿它来做比较呢?一来在笔者心中它一直是规则引擎界的标杆,有很多理念值得学习;二来是因为笔者只是熟悉它,其他框架没用好过。
但总体来说,作为国产规则引擎的后起之秀,在设计理念和支持上显然更胜一筹。编排规则引擎作为规则引擎的新方向,会继续探索,希望大家能够支持这款国产规则引擎。在编排方向上,除了文章中提到的一些特性外,还有很多其他的探索玩法和高级特性,是一个值得深入探索的框架。