购买
下载掌阅APP,畅读海量书库
立即打开
畅读海量书库
扫码下载掌阅APP

第3章
初识Drools规则引擎

从本章开始我们将结合前面讲到的理论知识,来学习Drools规则引擎这套框架的实践和运用。初学者在使用Drools时往往会遇到一些困惑,主要集中在不知道如何上手,上手后不知道如何跟业务相结合,以及如何构建规则管理平台方面。本章先为大家梳理整体思路,然后从构建一个简单的实践场景开始,循序渐进地带领大家了解如何使用Drools规则引擎。

Drools 8为目前的最新版本,但鉴于大多数项目还采用Drools 7,且Drools 8向下兼容Drools 7的语法,因此本章会采用一个示例,在此示例中既会介绍在Drools 6、7、8中均适用的传统语法风格,也会介绍在Drools 8中引入的新语法风格(Rule Unit,规则单元)。在后续章节中,我以传统语法风格和规则单元语法(或Drools 8语法)风格来命名及区分二者。

在使用Drools 8时,官方推荐采用规则单元语法风格来实现,特别是需要将Drools 8部署到Kogito等云原生组件当中时。但传统语法风格在构建规则管理平台方面还是有很大优势的。因此,在具体实践中采用传统语法风格还是规则单元语法风格,要视项目的具体情况而定。

本章会对两种风格的使用逐一讲解,大家可对照学习,这个过程也算是项目版本迁移时的一个简单示例。在后续章节中,我会对Drools 8的传统语法风格与规则单元语法风格并行讲解,以便大家能够兼顾各类应用场景。如果可能,大家尽量使用规则单元语法风格。下面,我们就来学习第一个示例吧。

3.1 如何循序渐进地学习

在学习Drools之前,我们先汇总实践中通常会遇到的一些难题,然后在后续章节中逐一击破。学习和使用Drools往往会遇到以下难题:

开发工具的选择 。是选择Eclipse,还是IDEA,抑或是VS Code?在Drools 7及以前版本中,官方推荐使用Eclipse,插件支持比较丰富,官方文档也是基于Eclipse进行讲解的。在Drools 8中,Eclipse、IDEA和VS Code对Drools的支持已经不分伯仲,选择适合自己的IDE即可。Drools的使用本质上就是对Drools提供的API和语义模板(规则文件等)语法的使用,IDE的影响仅限于语义模板语法解析方面,当大家对其有一定的了解之后,这一方面的影响便很小了。所以,对于IDE的选择,根据个人习惯选择即可。鉴于官方已经有基于Eclipse的使用说明,同时IDEA又是目前主流的IDE,本书便以IDEA来讲解,以作为补充。IDEA中也有Drools的插件支持,但部分语法还存在一些小瑕疵,比如会误报语法解析错误,使用时以运行结果为准即可。

核心API和规则语法的使用 。Drools规则引擎提供了一定数量的核心API,它们的功能在一定程度上有重叠,主要是为了支持更丰富的场景,不同场景下的使用会有所区别。这就很容易造成学习者的困惑:该选哪个API,该怎么用?后续在实战、自主开发管理后台等部分,我会对实战中常用的一些API和语法知识进行讲解。对于不常用的API及规则语法,大家在厘清思路之后参考官方文档即可。本书不会进行大量语法知识点(简称语法点)的罗列,而会将重点放在实践、架构、设计、示例等方面。

如何在项目中进行实践,或者说如何构建自己的BRMS 。学习Drools的过程中,大家可以很轻松地写出一个示例来验证某个语法,但从具体的语法使用到灵活运用再到项目实战,还有一定的跨度。如果没有接受实战指导,大家很容易陷入误区和困惑当中。

❑ 随着Drools 8向微服务组件、云原生靠拢,实践的最后环节便是在微服务、云原生组件中集成部署Drools。

综上所述,后续会先通过一些示例介绍常见语法的使用,待大家熟悉了项目结构、基本语法之后,再逐步进入项目实战经验的讲解。

下面就像学习每一种编程语言或框架一样,大家先来编写Drools的第一个示例吧。

3.2 创建第一个Drools项目

从零开始创建一个简单Drools项目,通常包含以下步骤:环境准备、创建项目、业务实现、运行验证。下面就围绕这四个步骤来进行演示。

在开始之前,先对本书示例中的Drools版本做一个简单的说明。第一个示例我使用Drools 8的8.33.0.Final版本,采用传统语法风格来编写规则示例,以便大家快速理解。这里也会拓展讲解基于Drool 7的7.70.0.Final版本及更早版本该如何配置依赖类库。这个过程也可以看作将传统语法风格从Drools 7迁移至Drools 8的升级过程。Drools 7的说明仅起到对照作用,后续章节示例如无特殊说明均采用8.33.0.Final版本。

另外,由于Drools社区比较活跃,版本更新频次比较高,几乎每一两个月就会更新一个版本,甚至在一个月左右更新两个版本。但无论是在项目实践中,还是在学习本书时,我们都没必要跟着版本更新的节奏来,可查看版本的发布说明(Release Notes)判断是否有必要升级。我们通常只需把握所需核心功能和基本使用流程。

3.2.1 环境准备

环境准备通常包含JDK、Maven、IDEA以及IDEA的Drools插件安装等。

Drools 8要求最低JDK版本为11,最低Maven版本为3.6.8。Drools 7对环境的要求为Java 8+和Maven 3.5.x+。后续示例中,如无特殊说明,所有的环境均为JDK 11和Maven 3.6.8,该环境也可正常运行Drools 7的项目。

JDK、Maven、IDEA等基础环境或开发工具的安装和配置,是一个开发人员必备的技能,故这里不再赘述。

这里简单介绍一下IDEA中Drools插件。在2018及以后版本的IDEA中,默认已经绑定(Bundled)了Drools的插件,不需要手动安装了。IDEA中的Drools插件,如图3-1所示。

图3-1 IDEA中的Drools插件

如果你所使用的IDEA版本中没有默认绑定Drools插件,可在IDEA中依次找到“Pre ferences”→“Plugins”→“Marketplace”,然后搜索“drools”,找到对应插件并进行安装。早期的插件名称叫JBoss Drools Support,后改为Drools。如果你的业务还用到了jBPM与Drools集成,也可在这里搜“JBoss”,安装“JBoss jBPM”等相关插件。

插件安装完成,便可以开始项目的创建了。

3.2.2 创建项目

Drools项目的创建步骤很简单,先创建一个基于Maven的普通Java项目,然后引入Drools的核心依赖即可。比如,我们可以通过如下命令直接创建一个简单的Maven项目。

当然,我们也可以通过IDE进行创建,这里就不再逐一介绍了。剩下的就是添加pom.xml中的Drools依赖类库了。

整个项目的pom.xml完整依赖如下:

在上述配置中,通过properties中的子元素定义Maven采用的是JDK 11,项目编码为UTF-8,Drools版本为8.33.0.Final。在build(构建)部分定义了采用maven-compiler-plugin和kie-maven-plugin插件进行编译项目的编译构建。其中kie-maven-plugin会在构建过程中检验项目的KIE资源的合法性,推荐使用。

除了上述配置,最重要的便是Drools的类库依赖了,这里引入了drools-engine、drools-mvel和drools-xml-support。其中drools-engine间接引入了kie-api、drools-core、drools-compiler等类库。在Drools 8中,为了使Drools核心引擎更加轻量级和易于维护,传统的核心类库被拆分。比如,将读取kmodule.xml的类库拆分为drools-xml-support依赖类库,如果没有引入drools-xml-support依赖类库则无法解析kmodule.xml文件。drools-mvel则是Drools引擎用于支持.drl文件解析、约束评估和模板生成等功能的,在Drools 7.45.0之前内置于drools-core中,之后也被抽离为独立的模块。

另外,drools-engine还间接引入了其他依赖类库,这里也顺便讲解一下。

❑ kie-api:提供了接口和工厂(factory),有助于清晰定义用户API和引擎API。

❑ kie-internal:提供内部接口和工厂。

❑ drools-core:这个包是Drools核心引擎和运行时组件,包括Rete引擎和Leaps引擎。如果是基于规则预编译的方式,运行时仅需这一个依赖。使用时需要依赖kie-api和kie-internal。

❑ drools-compiler:用于编译和构建规则,可用于运行时环境,但如果规则是预编译的,则该组件为非必需的。

如果在项目中还使用了决策表,则还会涉及drools-decisiontables的依赖,它用于决策表的编译组件,底层也使用到了drools-compiler,支持Excel和CSV两种形式的规则输入。

上述pom.xml的依赖为Drools 8中传统语法风格所需的依赖,在Drools 6和Drools 7的早期版本中,相关依赖被糅合在一起,pom.xml的配置通常如下:

在Drools 7.45.0之前drools-mvel还没从drools-core中拆分出来,如果使用与其对应的版本,可将drools-mvel依赖去掉,只依赖drools-core和drools-compiler即可。

从Drools 7.45.0开始,drools-engine和drools-engine-classic被引入,作为Drools的依赖类库。pom.xml的配置如下:

但从Drools 8开始,drools-engine-classic和drools-mvel被逐步废弃,替代它们的是drools-engine,在项目中只配置drools-engine依赖即可。但上面也提到,使用传统语法格式时,需要xml解析和mvel语言的类库支持,因此仍需要单独添加。

至此,关于项目创建和依赖添加部分就介绍完了。由于版本更迭,类库进行了多次重组,大家可按照所使用的版本进行类库的调整。需要注意的是,如果类库使用不当,就会出现莫名其妙的问题和异常。

项目的基础部分搭建完毕,下一步就是编写业务实体类、业务规则、配置文件和API的调用了。

3.2.3 业务实现

这里我以金融系统中最常见的用户评分为例(已进行场景简化),为了以最简便方式帮大家理解本节的主要内容,这里只涉及评分模型的一个维度——用户年龄。基于用户年龄的风控评估规则如图3-2所示。

图3-2 基于用户年龄的风控评估规则

图3-2所示的规则基于用户年龄,评估不同年龄段用户风险承受能力大小,评估得分(score)越高,风险承受能力越大。比如小于18岁时,评估得分为0,直接拒绝后续业务,其他年龄段得分依此类推。

下面来看具体的代码实现:创建一个用来传递年龄信息和接收最终结果的Fact对象,即一个普通的JavaBean,命名为Person。

其中age字段为业务系统输入信息的承载字段,score和desc作为规则引擎处理完之后输出信息的承载字段。在实践中Person类通常会包含更多属性(比如,姓名、收入、资产等),当然也可以添加更多的返回信息字段,甚至单独用一个对象来接收返回信息。

有了Fact对象和业务场景,就可以在.drl文件中编写基于Drools的业务规则了。规则文件通常就是一个普通的文本文件,扩展名为.drl,这也是Drools默认的规则文件格式。在项目的resources目录下创建rules目录,用其来统一存放规则文件,创建一个名称为score.drl的规则文件,针对上述业务逻辑,对应的规则代码实现如下:

在resources目录下创建一个名为META-INF的目录,并在该目录下创建一个名为kmodule.xml的文件,文件内容如下:

Drools会默认扫描resources/META-INF/kmodule.xml配置文件,加载其中的配置项,而且此配置文件也是必需的。上述代码中配置了一个名为“score-rule”的KieSession,该配置在API调用时会用到。

3.2.4 运行验证

经过上述步骤,规则和相应的配置都完成了,可以通过Drools的API将业务逻辑和规则的运行进行整合。这里创建一个ScoreTest类,在main方法中进行调用,对应代码实现如下:

运行上述代码,打印结果为:

至此,第一个示例编写完成。执行测试,成功触发规则,并获得最终规则引擎处理之后的结果信息。此时,你可能还有些困惑:这些配置和API都有什么作用呢?下一节对上述示例中的一些基础概念和知识进行详细的讲解。

3.3 项目结构详解

先看一下第一个示例项目的目录结构,主要包括事实对象、规则文件、文件配置和API使用。

如果简单描述第一个示例各部分的功能,那就是:通过Drools提供的API先加载和初始化kmodule.xml中指定的规则(score.drl),将其装载到KieContainer当中。通过KieContainer创建一个名为“score-rule”的KieSession,使用KieSession与规则引擎交互,传递Fact对象(Person)给规则引擎,并由规则引擎进行判断和执行,最后将执行的结果在控制台打印出来。

下面对示例中涉及的各个部分,进行详细的介绍。

3.3.1 事实对象

事实对象就是一个普通的JavaBean,承载了规则引擎与业务之间传输数据(业务数据及规则执行结果)的作用。在规则文件中可以通过import语法引入,在Drools的Working Memory(工作内存)中可以创建和修改事实对象。

事实对象是一个引用对象,在规则引擎之外和规则引擎的工作内存中都可以通过引用访问同一个对象。也就是说,调用规则引擎之前创建一个Person对象,然后将其传入规则引擎,规则引擎并不会复制一份,而是在原有对象基础上进行操作。这样就达到了规则引擎内外对象及状态的一致性,极大地方便了数据的传输和结果的获取。

不仅如此,在调用KieSession的insert方法时,还会返回一个FactHandler对象,它相当于工作内存中事实对象的句柄。

通过FactHandler对象,可以对工作内存中的事实对象进行直接定位、修改和删除等操作。以删除为例,部分代码如下:

执行上述代码,会发现通过delete方法的操作之后,工作内存中便不再存在对应的事实对象了,再次调用fireAllRules方法也就不会触发对应的规则了。关于FactHandler的修改、更新等操作,大家可自行尝试。

3.3.2 规则文件

规则文件,就是抽离出来的业务具体实现。在Drools中,规则文件默认就是文本文件,它支持多种扩展名,比如*.drl、*.xml、*.drls等。如果使用了决策表的类库,它还可以是*.xls或*.xlsx文件格式。实践中使用比较多的是.drl文件,后续基本上都是基于.drl规则文件格式及语法展开的。

规则文件有一定语法要求和基本组成,通常包含以下元素:package、package-name、imports、globals、functions、queries、rules。其中,imports、globals、functions、queries、rules并非语法关键字,而是它们的复数形式,代表着在同一个规则文件中可以出现多次。当然,上述所有元素都是可选项,是否使用完全取决于业务需要。

package用于指定规则文件的包名,对于一个规则而言package是必须定义的,而且必须放在规则文件的第一行。package的定义类似Java中包的定义,与Java不同的是,它不需要与物理路径保持一致,只是用于逻辑上的区分。

import用于导入规则文件所需的外部变量,比如类或静态方法。它的使用与Java中的import基本相同,不同的是import导入的不仅可以是类,还可以是类中的某一个可以访问的静态方法。

import导入类的静态方法的方式如下:

global用于定义全局变量。首先,在代码中通过Kie会话(KieSession)设置Drools工作内存中的全局变量名称和值。然后,在.drl规则文件中通过global声明全局变量,该全局变量便可以在规则中使用了。比如,可以将List(列表)数据、基础服务等设置为全局变量,以便在规则中使用。此处不再展开,后续章节会介绍具体的示例。

function用于自定义函数,它与Java中将功能封装成独立的方法类似,用于提升功能及逻辑的复用性。与Java不同的是,该自定义函数是通过function关键字来定义的。

query用于从Drools工作内存中查询符合规则的Fact对象。通过在.drl文件中定义query,应用程序可以通过KieSession#getQueryResults方法来获取匹配结果。这样不用遵循when或then规范,就可以从工作空间中获取符合条件的Fact对象。query名称对于KIE来说是全局的,所以必须确保项目中所有规则的query名称是唯一的。

.drl文件中query定义格式如下:

在代码中可以通过KieSession的getQueryResults方法来调用:

除了上述这些基础语法、函数之外,最重要的当属rule部分了,它是与业务逻辑关系最密切的部分,即rule规则体。

rule规则体的基本语法格式如下:

规则体以rule关键字开头,以end结尾。其中rule关键字后通过双引号定义规则名称,attributes用于定义规则的属性,比如常见的salience(优先级)、agenda-group(分组)、dialect(方言)等。when与then之间为规则判断的条件,通常称作LHS(Left Hand Side),then与end之间为规则触发的操作(行为),通常称作RHS(Right Hand Side)。其中,LHS和RHS的概念及功能我在第2章中已经讲过。

目前大家先简单了解规则文件的大体组成即可,关于每个语法点的细节可查阅官方文档,后面章节也会挑选常见的语法以示例进行讲解。

3.3.3 kmodule.xml配置

kmodule.xml是Drools的核心配置文件,它的作用类似Spring中的spring.xml或Logback中的logback.xml。在Drools中kmodule.xml主要为KIE项目提供声明式配置,比如配置规则(知识)库、规则会话等。

Drools默认会到META-INF目录下加载kmodule.xml文件,因此,通常需要在项目中提供对应的配置文件,并将其放于src/main/resources/META-INF/目录下,否则会抛出异常。注意,这里说的是“通常”,在某些场景下,也可以直接拼接kmodule.xml文件内容的字符串来实现,后面章节会有对应的示例。

3.3.4 API使用

示例中主要用到了三个核心的API:KieServices、KieContainer、KieSession。

其中,KieServices是KIE项目的中心,通过它可以访问所有KIE项目在构建和运行时所需的接口,比如KieContainer、KieFileSystem、KieResources等。通过KieServices获取到的各类对象,可以完成规则的构建、管理和执行等。一般通过KieServices.Factory.get()方法获得KieServices的实例对象,这种方法是基于线程安全的单例模式。

KieContainer是一个KieBase的容器,通过KieContainer可以加载KieModule及其依赖项。KieContainer也提供了获取KieSession、KieBase、KieBaseModel等对象的方法。一个KieContainer中可以包含一个或多个KieBase。KieContainer实例一般通过KieServices获得,且全局唯一。通常情况下,我们采用KieContainer来创建KieSession(有状态会话)和StatelessKieSession(无状态会话)。

KieSession是与规则引擎交互的会话类,与Web开发中的Session有点儿类似。通过它可以与规则引擎通信,并发起执行规则的操作。KieSession可通过KieContainer方便地创建,但本质上是从KieBase中创建的。我们与工作内存进行Fact对象的传输、规则的触发都以KieSession作为入口。

上面介绍了示例中涉及的一些API,也是传统语法风格中比较常用的API。大家在后面章节中还会经常见到,多实践几遍便可熟练运用。

3.4 Drools 8语法示例

在Drools 8中官方强烈推荐采用新的语法风格——规则单元(Rule Unit)。下面我就以Drools 8这种新的语法风格来重新实现上述示例。大家可对比两种具体实现的不同,同时也可以体验如何将传统语法升级为Drools 8的规则单元语法。

Drools 8新语法的实践过程与传统语法基本一致,也分为环境准备、创建项目、业务实现和运行验证。

其中,环境准备工作与前面的完全一致,这里不再赘述,下面直接从项目创建开始。

3.4.1 创建Drools 8项目

Drools 8的项目创建思路整体可分为两种:第一种,先创建基于Maven管理的Java项目,然后在pom.xml中添加Drools 8的依赖;第二种,直接通过Maven Archetype(原型)创建,然后根据需要删除或修改Maven Archetype的示例代码。

针对第一种思路,我们在前面的示例中已经实践过了,可参照创建Maven项目。针对第二种思路,官方文档中也提供了对应的命令。执行以下命令便可创建一个基于规则单元语法风格的项目。

基于Maven Archetype创建命令如下:

上述Maven命令基于kie-drools-exec-model-ruleunit-archetype的Maven Archetype,创建了一个版本为8.33.0.Final的Drools项目,并将项目的依赖直接添加到pom.xml文件当中。

在创建的过程中会下载Maven和Drools的依赖组件,同时需要交互输入Maven项目信息。根据提示输入项目的GroupId、ArtifactId和Version(三者简称GAV),完成项目创建。

查看项目的pom.xml文件,除引入了单元测试(junit)和日志(logback)依赖类库之外,重点引入了Drools 8的drools-ruleunits-engine依赖。

前面我们看到在Drools 8中使用传统语法风格引入的核心依赖是drools-engine,如果需要使用规则单元,则需要引入drools-ruleunits-engine依赖。规则单元是作为一个独立的依赖模块而存在的,因此,使用者可以通过依赖,自主选择两种不同的语法风格。

通过执行Maven Archetype创建项目后,在此版本下(后续可能会改进),执行项目中的单元测试方法,会发现程序无法正常运行,根据提示信息可知,还需再引入drools-wiring-dynamic才行。

添加上述依赖后,执行单元测试,可成功执行。如果你采用的是第一种方案,那么直接添加上述两个类库的依赖即可。

通过Maven Archetype创建项目会有一部分示例的代码,我们可参考其编写自己的业务代码,这里先将其删除,然后直接根据业务需求来编写自己的业务逻辑。

3.4.2 业务实现

业务场景和逻辑完全与前文相同,逻辑描述在此不再赘述,这里直接来看具体的代码实现。用于向规则引擎传递事实对象的JavaBean依旧是Person类,未做任何改变。

定义一个规则单元类PersonUnit,该类实现了RuleUnitData接口。

规则单元类可用来整合数据源、全局变量和规则。在PersonUnit类中,主要使用了数据源(DataStore)来存储要插入(insert)规则引擎中的事实对象。当然,你还可以定义一些其他类变量,作为全局变量来使用。

对应的score.drl文件规则内容如下:

在上述规则文件中,注释部分为传统语法风格,与规则单元的语法相对应。规则单元的语法在IDE中可能会有错误提示,但不影响最终执行。

其中,通过unit关键字,定义了该规则所使用的规则单元类为PersonUnit。规则中的“/persons”绑定到了PersonUnit类的persons变量上。如果PersonUnit中还需要其他类似变量(比如Set<String>),可采用类似DataStore<Person>变量的写法来进行声明,而这些变量不再需要在规则文件中声明为global的,便可直接使用。也就是说,传统的global方式可被规则单元类的成员变量方式所替代。

另外,规则文件与传统语法的主要不同就是在LHS中使用了OOPath标记符语法,等于是将原来的Person类名,换成了规则单元类PersonUnit中DataSource的变量名(persons)。

当完成了实体类、规则单元、DRL文件的编写时,就已经完成了规则的基本定义,剩下的就是通过API来使用规则了。有心的读者可能已经留意到,在规则单元语法风格中不再需要kmodule.xml配置文件了。

通过单元测试类PersonRuleTest来执行规则的调用,代码如下:

执行上述代码,会发现执行结果与传统语法风格的结果一样。对比传统语法风格的API和规则单元的API,会发现使用规则单元时,RuleUnitInstance替代了KieSession,用添加事实对象到DataSource变量代替了KieSession的insert操作。已经不再需要传统的KIE API,比如KieServices、KieContainer、KieBase。当然,这里只是不再需要使用者显式地调用这些API,在规则单元的底层实现中,还是会用到部分API的。

至此,基于Drools 8的规则单元语法风格示例讲解完毕。我们已经搭建起了开发环境,项目也跑起来了,对项目的基本组成也有了一定了解,也为后续学习和实践做好了铺垫。接下来我将用更丰富的示例,来讲解使用Drools时的一些核心语法及API。 Qrf3UXNquFtQqqqojFpxl7rwJCqPIT3prGo9kxAaZIuMdiBGCmjuCgYTLdgaz7PY

点击中间区域
呼出菜单
上一章
目录
下一章
×