Drools规则引擎:让复杂业务规则从代码中解耦

在企业级应用开发中,经常会遇到大量复杂且频繁变化的业务规则,例如会员折扣、订单优惠、风险评估、贷款审批以及动态定价等。

如果直接使用大量 if-else 实现这些逻辑,随着业务不断发展,代码会变得越来越复杂。规则之间相互交叉时,修改一项规则甚至可能影响其他业务逻辑。

Drools 是一款基于 Java 的开源规则引擎,可以将业务规则从普通 Java 代码中分离出来,使复杂的判断逻辑更加清晰,也方便后续维护和扩展。

本文将介绍 Drools 的核心概念,并通过一个订单折扣案例演示 Drools 的基本使用方法。

文章目录

    • [一、什么是 Drools?](#一、什么是 Drools?)
    • [二、Drools 的优势](#二、Drools 的优势)
      • [2.1 业务规则与程序代码解耦](#2.1 业务规则与程序代码解耦)
      • [2.2 规则更容易维护](#2.2 规则更容易维护)
      • [2.3 支持复杂的规则组合](#2.3 支持复杂的规则组合)
      • [2.4 方便扩展](#2.4 方便扩展)
      • [2.5 支持状态推理](#2.5 支持状态推理)
    • [三、Drools 的核心概念](#三、Drools 的核心概念)
      • [3.1 Fact](#3.1 Fact)
      • [3.2 Rule](#3.2 Rule)
      • [3.3 KieContainer](#3.3 KieContainer)
      • [3.4 KieBase](#3.4 KieBase)
      • [3.5 KieSession](#3.5 KieSession)
      • [3.6 Agenda](#3.6 Agenda)
    • [四、创建 Drools 项目](#四、创建 Drools 项目)
      • [4.1 添加 Maven 依赖](#4.1 添加 Maven 依赖)
    • 五、创建订单实体类
    • [六、编写 Drools 规则](#六、编写 Drools 规则)
      • [6.1 salience](#6.1 salience)
      • [6.2 activation-group](#6.2 activation-group)
    • [七、配置 KIE 模块](#七、配置 KIE 模块)
    • 八、执行规则
    • [九、常用 DRL 语法](#九、常用 DRL 语法)
      • [9.1 多条件判断](#9.1 多条件判断)
      • [9.2 逻辑或](#9.2 逻辑或)
      • [9.3 绑定属性](#9.3 绑定属性)
      • [9.4 修改 Fact](#9.4 修改 Fact)
      • [9.5 插入和删除 Fact](#9.5 插入和删除 Fact)
    • [十、KieSession 与 StatelessKieSession 的区别](#十、KieSession 与 StatelessKieSession 的区别)
      • [10.1 KieSession](#10.1 KieSession)
      • [10.2 StatelessKieSession](#10.2 StatelessKieSession)
    • [十一、Drools 的常见应用场景](#十一、Drools 的常见应用场景)
    • [十二、Drools 最佳实践](#十二、Drools 最佳实践)
      • [12.1 一条规则只处理一个明确目标](#12.1 一条规则只处理一个明确目标)
      • [12.2 使用有业务含义的规则名称](#12.2 使用有业务含义的规则名称)
      • [12.3 避免在 then 中编写复杂业务代码](#12.3 避免在 then 中编写复杂业务代码)
      • [12.4 控制规则之间的依赖](#12.4 控制规则之间的依赖)
      • [12.5 不要滥用高优先级](#12.5 不要滥用高优先级)
      • [12.6 为规则编写单元测试](#12.6 为规则编写单元测试)
      • [12.7 记录规则执行情况](#12.7 记录规则执行情况)
      • [12.8 注意会话的线程安全](#12.8 注意会话的线程安全)
    • [十三、Drools 的不足](#十三、Drools 的不足)
    • 十四、总结
    • 参考资料

一、什么是 Drools?

Drools 是一个开源的业务规则管理系统,其核心能力是根据输入的数据匹配业务规则,并执行满足条件的规则。

在传统代码中,我们可能会这样编写折扣逻辑:

java 复制代码
if ("VIP".equals(order.getCustomerType())
        && order.getOriginalPrice() >= 1000) {
    order.setDiscountRate(0.20);
} else if ("NORMAL".equals(order.getCustomerType())
        && order.getOriginalPrice() >= 1000) {
    order.setDiscountRate(0.10);
} else {
    order.setDiscountRate(0);
}

当规则数量较少时,这种写法没有明显问题。但如果系统中存在几十甚至上百条规则,代码中就会出现大量嵌套判断,维护难度也会迅速增加。

使用 Drools 后,可以将规则写在独立的 .drl 文件中:

text 复制代码
rule "VIP订单满1000元享八折"
when
    $order : Order(
        customerType == "VIP",
        originalPrice >= 1000
    )
then
    $order.setDiscountRate(0.20);
end

Java 代码负责提供数据,Drools 负责匹配并执行规则,从而实现业务代码与规则逻辑的分离。

二、Drools 的优势

2.1 业务规则与程序代码解耦

业务规则存放在独立的 DRL 文件中,不需要将所有判断逻辑都写在 Java 代码里。

2.2 规则更容易维护

每条规则通常由条件和结果组成,结构比多层 if-else 更清晰。

2.3 支持复杂的规则组合

Drools 支持优先级、规则分组、规则互斥、时间规则和事件处理等功能,适合处理复杂的业务判断。

2.4 方便扩展

新增业务规则时,通常只需要增加新的规则文件或规则内容,不需要大范围修改原有业务代码。

2.5 支持状态推理

在有状态会话中,一条规则可以修改或插入新的事实,从而继续触发其他规则,形成规则链。

三、Drools 的核心概念

3.1 Fact

Fact 表示需要交给规则引擎处理的业务数据,本质上通常是一个普通 Java 对象。当订单对象被插入 Drools 会话后,它就成为规则引擎中的一个 Fact。

3.2 Rule

Rule 表示一条业务规则,通常由规则名称、条件和动作组成:

text 复制代码
rule "规则名称"
when
    // 规则条件
then
    // 条件满足后执行的操作
end

其中,when 用于定义规则生效的条件,then 用于定义条件满足后执行的操作。

3.3 KieContainer

KieContainer 用于加载和管理项目中的规则资源。它会读取项目中的规则文件和 KIE 配置,并创建相应的 KieBase 和 KieSession。

3.4 KieBase

KieBase 是规则知识库,其中保存已经编译完成的规则、函数和类型定义。KieBase 本身不保存运行时业务数据。

3.5 KieSession

KieSession 是规则引擎的执行会话,负责接收 Fact、匹配规则、执行规则以及保存运行过程中的状态。

常见会话分为两类:

  • KieSession:有状态会话,会保留插入的数据和规则执行状态;
  • StatelessKieSession:无状态会话,每次执行相对独立,适合校验、计算和过滤等场景。

3.6 Agenda

当 Fact 满足规则条件时,符合条件的规则会进入 Agenda。Drools 会根据规则优先级和规则分组等配置决定实际执行顺序。

四、创建 Drools 项目

下面通过"订单折扣计算"案例演示 Drools 的基本使用方法。本文示例采用传统 KIE API,Drools 版本使用 10.1.0,JDK 使用 17。

项目结构如下:

text 复制代码
drools-demo
├── pom.xml
└── src
    └── main
        ├── java
        │   └── com/example/drools
        │       ├── DroolsApplication.java
        │       └── model/Order.java
        └── resources
            ├── META-INF/kmodule.xml
            └── rules/order-discount.drl

4.1 添加 Maven 依赖

在 pom.xml 中添加 Drools 依赖:

xml 复制代码
<properties>
    <maven.compiler.source>17</maven.compiler.source>
    <maven.compiler.target>17</maven.compiler.target>
</properties>

<dependencies>
    <dependency>
        <groupId>org.kie</groupId>
        <artifactId>kie-api</artifactId>
        <version>10.1.0</version>
    </dependency>

    <dependency>
        <groupId>org.drools</groupId>
        <artifactId>drools-engine</artifactId>
        <version>10.1.0</version>
    </dependency>

    <dependency>
        <groupId>org.drools</groupId>
        <artifactId>drools-xml-support</artifactId>
        <version>10.1.0</version>
    </dependency>
</dependencies>

kie-api 提供 KIE 相关接口,drools-engine 提供规则引擎的核心实现,drools-xml-support 用于支持 XML 形式的 KIE 配置。

五、创建订单实体类

创建 Order 类:

java 复制代码
package com.example.drools.model;

public class Order {

    private String customerType;
    private double originalPrice;
    private double discountRate;
    private double finalPrice;
    private String message;

    public Order() {
    }

    public Order(String customerType, double originalPrice) {
        this.customerType = customerType;
        this.originalPrice = originalPrice;
    }

    public String getCustomerType() {
        return customerType;
    }

    public void setCustomerType(String customerType) {
        this.customerType = customerType;
    }

    public double getOriginalPrice() {
        return originalPrice;
    }

    public void setOriginalPrice(double originalPrice) {
        this.originalPrice = originalPrice;
    }

    public double getDiscountRate() {
        return discountRate;
    }

    public void setDiscountRate(double discountRate) {
        this.discountRate = discountRate;
    }

    public double getFinalPrice() {
        return finalPrice;
    }

    public void setFinalPrice(double finalPrice) {
        this.finalPrice = finalPrice;
    }

    public String getMessage() {
        return message;
    }

    public void setMessage(String message) {
        this.message = message;
    }

    @Override
    public String toString() {
        return "Order{" +
                "customerType='" + customerType + '\\'' +
                ", originalPrice=" + originalPrice +
                ", discountRate=" + discountRate +
                ", finalPrice=" + finalPrice +
                ", message='" + message + '\\'' +
                '}';
    }
}

为了让示例保持简单,这里使用 double 表示金额。实际涉及金额计算的生产项目应优先使用 BigDecimal,避免浮点数精度问题。

六、编写 Drools 规则

在 src/main/resources/rules 目录下创建 order-discount.drl:

text 复制代码
package com.example.rules

import com.example.drools.model.Order

rule "VIP订单满1000元享八折"
    salience 20
    activation-group "order-discount"
when
    $order : Order(
        customerType == "VIP",
        originalPrice >= 1000
    )
then
    $order.setDiscountRate(0.20);
    $order.setFinalPrice($order.getOriginalPrice() * 0.80);
    $order.setMessage("VIP订单满1000元,享受8折优惠");
end

rule "普通订单满1000元享九折"
    salience 10
    activation-group "order-discount"
when
    $order : Order(
        customerType == "NORMAL",
        originalPrice >= 1000
    )
then
    $order.setDiscountRate(0.10);
    $order.setFinalPrice($order.getOriginalPrice() * 0.90);
    $order.setMessage("普通订单满1000元,享受9折优惠");
end

rule "默认不打折"
    salience 0
    activation-group "order-discount"
when
    $order : Order()
then
    $order.setDiscountRate(0);
    $order.setFinalPrice($order.getOriginalPrice());
    $order.setMessage("当前订单不满足优惠条件");
end

这里使用了两个重要的规则属性。

6.1 salience

salience 表示规则优先级,数值越大,规则通常越优先执行。在这个案例中,VIP 优惠规则优先级为 20,普通用户优惠规则优先级为 10,默认规则优先级为 0。

6.2 activation-group

同一个 activation-group 中,只要有一条规则被执行,其他已经激活的规则就会被取消。因此,一个订单只会执行一条折扣规则,避免优惠结果被默认规则覆盖。

七、配置 KIE 模块

在 src/main/resources/META-INF 目录下创建 kmodule.xml:

xml 复制代码
<?xml version="1.0" encoding="UTF-8"?>
<kmodule xmlns="http://www.drools.org/xsd/kmodule">
    <kbase name="orderKieBase"
           packages="com.example.rules"
           default="true">
        <ksession name="orderKieSession"
                  default="true"/>
    </kbase>
</kmodule>

packages 属性对应的是 DRL 文件顶部声明的包名,而不是 DRL 文件所在的文件夹路径。

八、执行规则

创建启动类 DroolsApplication:

java 复制代码
package com.example.drools;

import com.example.drools.model.Order;
import org.kie.api.KieServices;
import org.kie.api.runtime.KieContainer;
import org.kie.api.runtime.KieSession;

public class DroolsApplication {

    public static void main(String[] args) {
        KieServices kieServices = KieServices.Factory.get();
        KieContainer kieContainer =
                kieServices.getKieClasspathContainer();

        KieSession kieSession =
                kieContainer.newKieSession("orderKieSession");

        try {
            Order order = new Order("VIP", 1200);
            kieSession.insert(order);

            int firedRules = kieSession.fireAllRules();

            System.out.println("执行规则数量:" + firedRules);
            System.out.println("订单结果:" + order);
        } finally {
            kieSession.dispose();
        }
    }
}

运行程序后,可以得到类似结果:

text 复制代码
执行规则数量:1
订单结果:Order{customerType='VIP', originalPrice=1200.0,
discountRate=0.2, finalPrice=960.0,
message='VIP订单满1000元,享受8折优惠'}

代码执行流程如下:

  1. 创建 KieContainer;
  2. 从容器中创建 KieSession;
  3. 将订单对象插入会话;
  4. 调用 fireAllRules() 匹配并执行规则;
  5. 获取规则修改后的订单;
  6. 调用 dispose() 释放会话资源。

对于有状态的 KieSession,使用完成后应调用 dispose(),避免会话长期持有对象而造成资源泄漏。

九、常用 DRL 语法

9.1 多条件判断

text 复制代码
$order : Order(
    customerType == "VIP",
    originalPrice >= 1000
)

多个条件之间使用逗号分隔,表示逻辑与。

9.2 逻辑或

text 复制代码
$order : Order(
    customerType == "VIP" || customerType == "SVIP"
)

9.3 绑定属性

text 复制代码
$order : Order($price : originalPrice)

绑定后可以在 then 中使用 $price。

9.4 修改 Fact

在有状态会话中,如果希望修改 Fact 后重新触发规则匹配,可以使用 modify:

text 复制代码
modify($order) {
    setDiscountRate(0.20),
    setFinalPrice($order.getOriginalPrice() * 0.80)
}

modify 会通知规则引擎对象已经发生变化。使用时需要注意规则是否会再次匹配,避免形成无限循环。

9.5 插入和删除 Fact

可以通过 insert 插入新的 Fact,也可以通过 delete 删除 Fact。新对象插入后可能继续触发其他规则,从而形成规则链。

十、KieSession 与 StatelessKieSession 的区别

10.1 KieSession

KieSession 会保留会话状态,适合以下场景:

  • 多个 Fact 之间存在关联;
  • 一条规则会触发其他规则;
  • 需要修改、删除或持续维护 Fact;
  • 需要进行复杂推理;
  • 需要处理连续事件。

使用结束后应调用 dispose()。

10.2 StatelessKieSession

StatelessKieSession 更像一个普通函数:传入数据,执行规则,然后返回结果。

java 复制代码
StatelessKieSession session =
        kieContainer.newStatelessKieSession();

session.execute(order);

它适合数据校验、单次价格计算、消息分类、请求路由等不需要保存历史状态的决策。无状态会话执行 execute() 时,会自动完成数据插入、规则执行和会话清理。

十一、Drools 的常见应用场景

  • 营销优惠:根据会员等级、订单金额和活动时间计算优惠;
  • 风控系统:根据用户信用、交易金额和设备信息判断风险;
  • 审批流程:根据金额、部门和申请类型决定审批流程;
  • 保险金融:根据年龄、职业和历史记录计算保费或授信额度;
  • 数据校验:对提交的数据执行多项校验并返回失败原因;
  • 智能推荐:根据用户特征和业务策略匹配推荐结果。

十二、Drools 最佳实践

12.1 一条规则只处理一个明确目标

不要在一条规则中混合过多业务逻辑。规则越独立,后期测试和维护越容易。

12.2 使用有业务含义的规则名称

"VIP订单满1000元享八折"比"rule_001"更加容易理解和排查。

12.3 避免在 then 中编写复杂业务代码

数据库访问、远程调用和复杂计算最好放在 Java 服务中完成。规则负责做出决策,Java 服务负责执行具体操作。

12.4 控制规则之间的依赖

大量规则相互修改 Fact 时,可能形成难以理解的执行链。应合理使用 salience、agenda-group、activation-group 和 no-loop 等属性。

12.5 不要滥用高优先级

如果每条规则都依赖 salience 控制顺序,规则集会变得难以维护。规则优先级应该只用于确实存在业务先后关系的场景。

12.6 为规则编写单元测试

测试应覆盖满足条件、不满足条件、临界值、多条规则同时匹配以及空值或异常输入。

12.7 记录规则执行情况

可以通过 Agenda 事件监听器记录哪些规则被匹配和执行,避免在大量规则中直接编写 System.out.println()。

12.8 注意会话的线程安全

不要在多个请求线程之间随意共享同一个有状态 KieSession。常见做法是根据业务请求创建独立会话,或者使用会话池。

十三、Drools 的不足

Drools 并不适合所有项目,它也存在一定的使用成本:

  • DRL 具有独立语法,需要一定学习时间;
  • 规则数量较多时,执行顺序可能不容易理解;
  • 不合理的规则可能产生循环触发;
  • 排查问题通常比普通 Java 判断更加复杂;
  • 简单业务使用规则引擎可能属于过度设计;
  • 团队需要建立规则审核、测试和版本管理机制。

如果系统只有少量且几乎不会变化的判断逻辑,普通 Java 代码可能更加直接。当规则数量多、变化频繁,并且需要由多个业务维度共同决策时,Drools 才能更明显地体现价值。

十四、总结

Drools 的核心价值不是消除所有 if-else,而是将复杂、频繁变化的业务决策从程序流程中分离出来。

Drools 的基本使用流程可以概括为:

text 复制代码
定义业务对象
    ↓
编写 DRL 规则
    ↓
创建 KieContainer
    ↓
创建 KieSession
    ↓
插入 Fact
    ↓
执行规则
    ↓
获取处理结果
    ↓
释放会话资源

在实际项目中,引入 Drools 前需要认真判断业务是否具有规则复杂、变化频繁和需要统一管理等特点。如果只是为了替换几个简单的条件判断,引入规则引擎反而可能增加系统复杂度。

只有当规则确实需要独立管理、组合执行和持续扩展时,Drools 才是一把真正合适的工具。

参考资料

相关推荐
小葱运维1 小时前
日志规范(K8s 结构化日志)
运维·开源·云计算
周先生FullStack1 小时前
Mac + 容器本地 MySQL/Redis 环境搭建与网络原理实战
java·spring boot·微服务·容器·架构·个人开发
打工仔折腾 AI1 小时前
Pascal Editor 本地部署实战:Bun 启动 WebGPU 3D 编辑器并解决公网访问报错
人工智能·后端·python·性能优化
吠品1 小时前
Nginx负载均衡配置与线上故障排查的一些经验
java·开发语言·数据库
冬奇Lab1 小时前
DeepSeek Harness 系列(10):写一个完整的 dsh 插件——从需求到上线
人工智能·学习·开源
边境悍匪1 小时前
蜗牛学苑 Java 智能体学习 Day44|Vue 前端项目搭建 思维导图复盘
java·开发语言·spring boot·学习·阿里云
万年咸鱼2 小时前
public class 和 class 的区别详解
java
焦虑的说说2 小时前
redis知识汇总
java·redis