规则引擎从入门到实践

1. 引言 – 讲个故事

小明是一个兢兢业业的服务端程序员,有一天产品经理找到他说,我们要给用户发一条消息,消息的内容按照用户的积分分为三档,1000 以下的用户发:

“Sorry, you don’t have enough points”

1000 以上,10000 以下的用户发:

“Thanks, we will give a prety gift for you”

10000 以上的用户发:

“Congratulation, we invite you to participate in our online events”。

于是,小明写了代码:

public void sendText(Person person) {
 if (person.getScore() < 1000) {
   System.out.println("Sorry, you don't have enough points");
} else if (person.getScore() < 10000) {
   System.out.println("Thanks, we will give a prety gift for you");
} else {
   System.out.println("Congratulation, we invite you to participate in our online activities");
}
}

看起来代码很简单,小明高高兴兴地把代码部署上线,结果产品经理跑过来说,不行,咱的礼物不够,要把 1000 的阈值变 5000,10000 的阈值变 50000。

小明赶紧加班把这版改好上线,产品又来了,不行,文案不合适,需要调整。

这样三番五次一改再改,小明键盘一摔,对产品经理大吼:

git 权限给你开了,代码你来写!

你是否也有过这样的沉痛经历呢?更有甚者,经过一次又一次的修改,产品经理最终会把三个 if 扩充到十几个甚至几十个来回嵌套的 if 条件,这样的代码终将把你逼疯:

规则引擎从入门到实践
规则引擎从入门到实践

小明想:要是能让产品自己写代码实现这些逻辑就好了。

小明的想法可以实现吗?当然可以了,规则引擎就是用来解决这样问题的系统。

2. 专家系统与规则引擎

近来,低代码平台的概念被炒得火热,事实上这并不是一个新生概念,早在上世纪 60 年代,就已经诞生了通过编写简单的伪代码、表达式实现复杂的逻辑推理程序,这就是“专家系统”。

从设计理念上来看,专家系统与如今的低代码系统的初衷是一致的,那就是用程序加上学习成本很低的逻辑代码实现对专家的替代。

规则引擎就是一个用来简化代码逻辑的专家系统,用来分离商业决策者的商业决策逻辑和应用开发者的技术决策。通过将决策逻辑编写为更接近现实中语言的规则,存储在数据库或程序内存中,需要执行时取出规则并解析,从而实现小明让产品编写决策逻辑,动态修改的诉求。

比如:

rule "high score"
rule "high score"
   when
       Person(score >= 10000)
   then
       System.out.println("Congratulation, we invite you to participate in our online activities");
   end

rule "mid score"
   when
       Person(score >= 1000 && score < 10000)
   then
       System.out.println("Thanks, we will give a prety gift for you");
   end

rule "low score"
   when
       Person(score < 1000)
   then
       System.out.println("Sorry, you don't have enough points");
   end

你甚至可以设计一个交互界面,让产品在页面上轻松填写阈值与文案,自动生成规则存储在数据库中,这样,产品经理要添加或修改这些逻辑就再也不用找你了。

3. RETE 算法

根据上面的讲解,我们知道,规则的一般形式就是 IF – THEN 操作,通过条件判断与具体操作完成决策的定义。但实际上,每一个具体的规则都不是一个简单的决策,而是由一系列决策以各种方式组成的决策网络,RETE 算法就是高效构建决策网络的算法,RETE 得名于拉丁文中的“网络”一词。

Rete 算法最初是由卡内基梅隆大学的 Charles L.Forgy 博士在 1974 年发表的论文中所阐述的,他的核心并不复杂。

Rete 算法巧妙地将逻辑推理的过程总结成有向无环图,每个 if-then 判断都可以视为是一个节点,然后将所有的节点以一定的方式连接在一起,这就构成了一张有向无环图。你可以仔细思考一下,无论是多么复杂的推理逻辑,都可以用不同的图将推理过程绘制出来,只是分支多少可能有所区别,事实上,这就是所谓的“决策网络”。

一张有向无环图中最重要的就是各个节点的组织,RETE 算法将构成这个逻辑的有向无环图的节点分为以下几类:

  1. RootNode — 这张有向无环图的根节点;
  2. ObjectTypeNode — 对象类型节点,保证所传入的对象只会进入自己类型所在的网络;
  3. AlphaNode — 条件判断节点,只有符合条件才能向下传播;
  4. JoinNode — 连接节点,将两个分支进行连接,相当于 and 操作;
  5. NotNode — 过滤节点,过滤掉数组中不存在的元素;
  6. LeftInputAdapterNodes — 将单个对象转化为数组;
  7. TerminalNodes — 终结节点,说明已经完成所有条件的执行。

下面就是一个简单的 RETE 网络图:

规则引擎从入门到实践
规则引擎从入门到实践

4. 代码实践

基于 Rete 算法的规则引擎非常多,这里我们选用业内使用最为广泛的 Drools 来举例。

Drools 是在 Rete 算法基础上提出了 Rete 算法的面向对象版本 — ReteOO 算法,并实现了一套 java 版本的规则库。

4.1 maven 依赖

首先,需要引入 maven 依赖如下:

<!-- https://mvnrepository.com/artifact/org.drools/drools-compiler -->
<dependency>
 <groupId>org.drools</groupId>
 <artifactId>drools-compiler</artifactId>
 <version>${drools.version}</version>
</dependency>
<dependency>
 <groupId>org.drools</groupId>
 <artifactId>drools-templates</artifactId>
 <version>${drools.version}</version>
</dependency>

<dependency>
 <groupId>org.kie</groupId>
 <artifactId>kie-api</artifactId>
 <version>${drools.version}</version>
</dependency>

4.2 编写 drl 文件

package cn.techlog.testjava.main.drools.test

import cn.techlog.testjava.main.drools.activities.Person

rule "high score"
   when
       Person(score >= 10000)
   then
       System.out.println("Congratulation, we invite you to participate in our online activities");
   end

rule "mid score"
   when
       Person(score >= 1000 && score < 10000)
   then
       System.out.println("Thanks, we will give a prety gift for you");
   end

rule "low score"
   when
       Person(score < 1000)
   then
       System.out.println("Sorry, you don't have enough points");
   end

4.3 创建 drools 工具类

package cn.techlog.testjava.main.drools.util;

import cn.techlog.testjava.main.drools.video_edit.DroolsTest;

import java.io.*;
import java.net.URL;

public class DroolsUtil {
   public static String getDrlString(String drlFileName) {
       StringBuilder drlStringBuilder = new StringBuilder();
       try {
           URL url = DroolsTest.class.getClassLoader().getResource(drlFileName);
           File file = new File(url.getFile());
           Reader reader = new InputStreamReader(new FileInputStream(file));
           char[] buffer = new char[1024 * 1024];
           int bytes = reader.read(buffer);
           for (int i = 0; i < bytes; ++i) {
               drlStringBuilder.append(buffer[i]);
          }
      } catch (IOException e) {
           e.printStackTrace();
      }
       return drlStringBuilder.toString();
  }
}

4.4 编写测试代码

package cn.techlog.testjava.main.drools.activities;

import cn.techlog.testjava.main.drools.util.DroolsUtil;
import org.kie.api.io.ResourceType;
import org.kie.api.runtime.StatelessKieSession;
import org.kie.internal.utils.KieHelper;

public class DroolsTest {
   public static void main(String[] args) {
       String drlStr = DroolsUtil.getDrlString("activities.drl");
       KieHelper helper = new KieHelper();
       helper.addContent(drlStr, ResourceType.DRL);
       StatelessKieSession kieSession = helper.build().newStatelessKieSession();

       Person person = new Person();
       person.setScore(10);
       kieSession.execute(person);

       person.setScore(100000);
       kieSession.execute(person);

       person.setScore(5000);
       kieSession.execute(person);
  }
}

4.5 执行结果

Sorry, you don’t have enough points

Congratulation, we invite you to participate in our online activities

Thanks, we will give a prety gift for you

微信公众号

欢迎关注微信公众号,以技术为主,涉及历史、人文等多领域的学习与感悟,每周都有精彩推文,全部原创,只有干货没有鸡汤

发表评论

登录后才能评论
服务中心
服务中心
联系客服
联系客服
返回顶部