Drools规则引擎实战指南

Drools规则引擎实战指南:从入门到生产应用

前言

Drools是一个开源的业务规则管理系统(BRMS),基于Java开发,提供了强大的规则引擎能力。它将业务逻辑从代码中分离出来,使业务人员可以直接维护规则,提高了系统的灵活性和可维护性。本文将深入介绍Drools的核心概念和实战应用。

一、Drools核心架构

1.1 整体架构图

sql 复制代码
+----------------------------------------------------------+
|                    Drools Architecture                    |
|                                                          |
|  +------------------+      +------------------+          |
|  |  Rules (DRL)     |      |  Fact Model      |          |
|  |  +-----------+   |      |  +-----------+   |          |
|  |  | Rule 1    |   |      |  | Java POJO |   |          |
|  |  | Rule 2    |   |      |  | Objects   |   |          |
|  |  | Rule 3    |   |      |  +-----------+   |          |
|  |  +-----------+   |      +--------+---------+          |
|  +--------+---------+               |                    |
|           |                         |                    |
|           v                         v                    |
|  +--------+--------------------------+---------+         |
|  |         Knowledge Base (KieBase)           |         |
|  |  +--------------------------------------+  |         |
|  |  |  Compiled Rules + Patterns          |  |         |
|  |  +--------------------------------------+  |         |
|  +--------+---------------------------------+  |         |
|           |                                    |         |
|           v                                    |         |
|  +--------+---------------------------------+  |         |
|  |      Working Memory (KieSession)        |  |         |
|  |  +-----------------------------------+  |  |         |
|  |  |  Insert Facts -> Pattern Match   |  |  |         |
|  |  |  -> Execute Actions              |  |  |         |
|  |  +-----------------------------------+  |  |         |
|  +------------------------------------------+  |         |
|                                                |         |
|  +------------------------------------------+  |         |
|  |           Agenda (Rule Execution)        |  |         |
|  |  Rule1 [priority=10] -> Execute          |  |         |
|  |  Rule2 [priority=5]  -> Execute          |  |         |
|  +------------------------------------------+  |         |
+----------------------------------------------------------+

1.2 核心组件说明

lua 复制代码
组件层次结构:

KieServices
    |
    +-- KieContainer
            |
            +-- KieBase (Knowledge Base)
                    |
                    +-- KieSession (Working Memory)
                            |
                            +-- Facts (业务对象)
                            +-- Agenda (规则执行队列)

二、快速开始

2.1 Maven依赖配置

xml 复制代码
<!-- pom.xml -->
<properties>
    <drools.version>8.44.0.Final</drools.version>
    <spring.boot.version>3.1.5</spring.boot.version>
</properties>

<dependencies>
    <!-- Drools核心依赖 -->
    <dependency>
        <groupId>org.drools</groupId>
        <artifactId>drools-core</artifactId>
        <version>${drools.version}</version>
    </dependency>
    <dependency>
        <groupId>org.drools</groupId>
        <artifactId>drools-compiler</artifactId>
        <version>${drools.version}</version>
    </dependency>
    <dependency>
        <groupId>org.drools</groupId>
        <artifactId>drools-mvel</artifactId>
        <version>${drools.version}</version>
    </dependency>

    <!-- Decision Table支持 -->
    <dependency>
        <groupId>org.drools</groupId>
        <artifactId>drools-decisiontables</artifactId>
        <version>${drools.version}</version>
    </dependency>

    <!-- Spring Boot集成 -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter</artifactId>
        <version>${spring.boot.version}</version>
    </dependency>

    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <version>1.18.30</version>
    </dependency>
</dependencies>

2.2 项目结构

css 复制代码
drools-demo/
├── src/
│   ├── main/
│   │   ├── java/
│   │   │   └── com/example/drools/
│   │   │       ├── config/
│   │   │       │   └── DroolsConfig.java
│   │   │       ├── model/
│   │   │       │   ├── Order.java
│   │   │       │   ├── Customer.java
│   │   │       │   └── Product.java
│   │   │       ├── service/
│   │   │       │   └── RuleService.java
│   │   │       └── DroolsApplication.java
│   │   └── resources/
│   │       ├── rules/
│   │       │   ├── discount-rules.drl
│   │       │   ├── validation-rules.drl
│   │       │   └── promotion-rules.drl
│   │       ├── META-INF/
│   │       │   └── kmodule.xml
│   │       └── application.yml
│   └── test/
└── pom.xml

三、实体模型定义

3.1 订单实体

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

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;

@Data
@NoArgsConstructor
@AllArgsConstructor
public class Order {
    private Long orderId;
    private Customer customer;
    private List<OrderItem> items = new ArrayList<>();
    private BigDecimal totalAmount;
    private BigDecimal discountAmount = BigDecimal.ZERO;
    private BigDecimal finalAmount;
    private String status;
    private LocalDateTime orderTime;
    private String promotionCode;

    // 业务方法
    public void calculateTotalAmount() {
        this.totalAmount = items.stream()
                .map(OrderItem::getSubtotal)
                .reduce(BigDecimal.ZERO, BigDecimal::add);
    }

    public void applyDiscount(BigDecimal discount) {
        this.discountAmount = this.discountAmount.add(discount);
        this.finalAmount = this.totalAmount.subtract(this.discountAmount);
    }

    public int getItemCount() {
        return items.stream()
                .mapToInt(OrderItem::getQuantity)
                .sum();
    }
}

@Data
@NoArgsConstructor
@AllArgsConstructor
class OrderItem {
    private Product product;
    private int quantity;
    private BigDecimal price;
    private BigDecimal subtotal;

    public void calculateSubtotal() {
        this.subtotal = this.price.multiply(BigDecimal.valueOf(quantity));
    }
}

3.2 客户实体

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

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.time.LocalDate;

@Data
@NoArgsConstructor
@AllArgsConstructor
public class Customer {
    private Long customerId;
    private String name;
    private String level;  // VIP, GOLD, SILVER, NORMAL
    private int loyaltyPoints;
    private LocalDate memberSince;
    private boolean isNewCustomer;

    // 业务方法
    public int getMemberYears() {
        return LocalDate.now().getYear() - memberSince.getYear();
    }

    public boolean isVip() {
        return "VIP".equals(level);
    }

    public boolean isGold() {
        return "GOLD".equals(level);
    }
}

3.3 产品实体

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

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.math.BigDecimal;

@Data
@NoArgsConstructor
@AllArgsConstructor
public class Product {
    private Long productId;
    private String name;
    private String category;  // ELECTRONICS, CLOTHING, FOOD, BOOKS
    private BigDecimal price;
    private boolean onSale;
    private int stockQuantity;

    public boolean isInStock() {
        return stockQuantity > 0;
    }
}

四、Drools配置

4.1 KModule配置文件

xml 复制代码
<!-- src/main/resources/META-INF/kmodule.xml -->
<?xml version="1.0" encoding="UTF-8"?>
<kmodule xmlns="http://www.drools.org/xsd/kmodule">
    <kbase name="rules" packages="rules" default="true">
        <ksession name="ksession-rules" default="true" type="stateful"/>
    </kbase>
</kmodule>

4.2 Spring Boot配置类

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

import org.kie.api.KieServices;
import org.kie.api.builder.KieBuilder;
import org.kie.api.builder.KieFileSystem;
import org.kie.api.builder.KieModule;
import org.kie.api.runtime.KieContainer;
import org.kie.api.runtime.KieSession;
import org.kie.internal.io.ResourceFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.core.io.support.ResourcePatternResolver;

import java.io.IOException;

@Configuration
public class DroolsConfig {

    private static final String RULES_PATH = "rules/";

    @Bean
    public KieContainer kieContainer() throws IOException {
        KieServices kieServices = KieServices.Factory.get();
        KieFileSystem kieFileSystem = kieServices.newKieFileSystem();

        // 加载所有规则文件
        ResourcePatternResolver resourcePatternResolver =
                new PathMatchingResourcePatternResolver();
        Resource[] resources = resourcePatternResolver
                .getResources("classpath*:" + RULES_PATH + "**/*.drl");

        for (Resource resource : resources) {
            kieFileSystem.write(ResourceFactory.newClassPathResource(
                    RULES_PATH + resource.getFilename(), "UTF-8"));
        }

        KieBuilder kieBuilder = kieServices.newKieBuilder(kieFileSystem);
        kieBuilder.buildAll();

        KieModule kieModule = kieBuilder.getKieModule();
        return kieServices.newKieContainer(kieModule.getReleaseId());
    }

    @Bean
    public KieSession kieSession() throws IOException {
        return kieContainer().newKieSession();
    }
}

五、规则文件编写

5.1 折扣规则

java 复制代码
// src/main/resources/rules/discount-rules.drl
package rules

import com.example.drools.model.Order
import com.example.drools.model.Customer
import java.math.BigDecimal

// 规则1: VIP客户享受15%折扣
rule "VIP Customer Discount"
    salience 100  // 优先级
    when
        $order: Order(customer.level == "VIP")
    then
        BigDecimal discount = $order.getTotalAmount()
                .multiply(new BigDecimal("0.15"));
        $order.applyDiscount(discount);
        System.out.println("应用VIP折扣: " + discount);
        update($order);
end

// 规则2: 金卡客户享受10%折扣
rule "Gold Customer Discount"
    salience 90
    when
        $order: Order(customer.level == "GOLD")
    then
        BigDecimal discount = $order.getTotalAmount()
                .multiply(new BigDecimal("0.10"));
        $order.applyDiscount(discount);
        System.out.println("应用金卡折扣: " + discount);
        update($order);
end

// 规则3: 订单金额超过1000元享受8%折扣
rule "Large Order Discount"
    salience 80
    when
        $order: Order(totalAmount >= 1000)
    then
        BigDecimal discount = $order.getTotalAmount()
                .multiply(new BigDecimal("0.08"));
        $order.applyDiscount(discount);
        System.out.println("应用大额订单折扣: " + discount);
        update($order);
end

// 规则4: 新客户首单享受5%折扣
rule "New Customer First Order Discount"
    salience 70
    when
        $order: Order(customer.isNewCustomer == true)
    then
        BigDecimal discount = $order.getTotalAmount()
                .multiply(new BigDecimal("0.05"));
        $order.applyDiscount(discount);
        System.out.println("应用新客户折扣: " + discount);
        update($order);
end

// 规则5: 购买商品数量超过10件享受额外折扣
rule "Bulk Purchase Discount"
    salience 60
    when
        $order: Order(itemCount > 10)
    then
        BigDecimal discount = new BigDecimal("50");
        $order.applyDiscount(discount);
        System.out.println("应用批量购买折扣: " + discount);
        update($order);
end

5.2 促销规则

java 复制代码
// src/main/resources/rules/promotion-rules.drl
package rules

import com.example.drools.model.Order
import com.example.drools.model.OrderItem
import com.example.drools.model.Product
import java.math.BigDecimal
import java.time.LocalDateTime
import java.time.LocalTime

// 规则1: 电子产品类别促销 - 买2送1
rule "Electronics Buy 2 Get 1 Free"
    when
        $order: Order()
        $item: OrderItem(product.category == "ELECTRONICS", quantity >= 2) from $order.items
    then
        int freeItems = $item.getQuantity() / 2;
        BigDecimal discount = $item.getPrice()
                .multiply(BigDecimal.valueOf(freeItems));
        $order.applyDiscount(discount);
        System.out.println("电子产品买2送1,优惠: " + discount);
        update($order);
end

// 规则2: 促销码应用
rule "Promotion Code - SAVE20"
    when
        $order: Order(promotionCode == "SAVE20")
    then
        BigDecimal discount = $order.getTotalAmount()
                .multiply(new BigDecimal("0.20"));
        $order.applyDiscount(discount);
        System.out.println("应用促销码SAVE20,优惠: " + discount);
        update($order);
end

// 规则3: 限时促销 - 每天10-12点额外9折
rule "Time Limited Promotion"
    when
        $order: Order()
        eval(LocalTime.now().isAfter(LocalTime.of(10, 0)) &&
             LocalTime.now().isBefore(LocalTime.of(12, 0)))
    then
        BigDecimal discount = $order.getTotalAmount()
                .multiply(new BigDecimal("0.10"));
        $order.applyDiscount(discount);
        System.out.println("应用限时促销,优惠: " + discount);
        update($order);
end

// 规则4: 满减活动 - 满500减50
rule "Full Reduction 500-50"
    when
        $order: Order(totalAmount >= 500)
    then
        BigDecimal discount = new BigDecimal("50");
        $order.applyDiscount(discount);
        System.out.println("满500减50,优惠: " + discount);
        update($order);
end

// 规则5: 特定品类组合优惠
rule "Category Combo Discount"
    when
        $order: Order()
        exists(OrderItem(product.category == "ELECTRONICS") from $order.items)
        exists(OrderItem(product.category == "BOOKS") from $order.items)
    then
        BigDecimal discount = new BigDecimal("30");
        $order.applyDiscount(discount);
        System.out.println("电子产品+图书组合优惠: " + discount);
        update($order);
end

5.3 验证规则

java 复制代码
// src/main/resources/rules/validation-rules.drl
package rules

import com.example.drools.model.Order
import com.example.drools.model.OrderItem
import com.example.drools.model.Product

global java.util.List validationErrors

// 规则1: 库存验证
rule "Check Product Stock"
    when
        $order: Order()
        $item: OrderItem(product.stockQuantity < quantity) from $order.items
    then
        validationErrors.add("产品 " + $item.getProduct().getName() +
                           " 库存不足,库存: " + $item.getProduct().getStockQuantity() +
                           ",需求: " + $item.getQuantity());
        System.out.println("库存验证失败: " + $item.getProduct().getName());
end

// 规则2: 订单金额验证
rule "Minimum Order Amount"
    when
        $order: Order(totalAmount < 10)
    then
        validationErrors.add("订单金额不能少于10元");
        System.out.println("订单金额验证失败");
end

// 规则3: 订单项数量验证
rule "Maximum Item Quantity"
    when
        $order: Order()
        $item: OrderItem(quantity > 100) from $order.items
    then
        validationErrors.add("单个商品购买数量不能超过100件");
        System.out.println("商品数量验证失败");
end

// 规则4: VIP客户特殊权限
rule "VIP Special Product Access"
    when
        $order: Order(customer.level != "VIP")
        $item: OrderItem(product.name matches ".*VIP专享.*") from $order.items
    then
        validationErrors.add("VIP专享商品仅限VIP客户购买");
        System.out.println("VIP权限验证失败");
end

六、规则服务实现

6.1 规则引擎服务

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

import com.example.drools.model.Order;
import lombok.extern.slf4j.Slf4j;
import org.kie.api.runtime.KieContainer;
import org.kie.api.runtime.KieSession;
import org.springframework.stereotype.Service;

import java.util.ArrayList;
import java.util.List;

@Slf4j
@Service
public class RuleService {

    private final KieContainer kieContainer;

    public RuleService(KieContainer kieContainer) {
        this.kieContainer = kieContainer;
    }

    /**
     * 执行折扣规则
     */
    public Order applyDiscountRules(Order order) {
        KieSession kieSession = kieContainer.newKieSession();

        try {
            log.info("开始执行折扣规则,订单ID: {}", order.getOrderId());

            // 插入事实对象
            kieSession.insert(order);

            // 触发所有规则
            int firedRules = kieSession.fireAllRules();

            log.info("执行了 {} 条规则", firedRules);
            log.info("原始金额: {}, 折扣金额: {}, 最终金额: {}",
                    order.getTotalAmount(),
                    order.getDiscountAmount(),
                    order.getFinalAmount());

            return order;
        } finally {
            kieSession.dispose();
        }
    }

    /**
     * 验证订单
     */
    public List<String> validateOrder(Order order) {
        KieSession kieSession = kieContainer.newKieSession();
        List<String> validationErrors = new ArrayList<>();

        try {
            log.info("开始验证订单,订单ID: {}", order.getOrderId());

            // 设置全局变量
            kieSession.setGlobal("validationErrors", validationErrors);

            // 插入事实对象
            kieSession.insert(order);
            order.getItems().forEach(kieSession::insert);

            // 触发所有规则
            kieSession.fireAllRules();

            if (validationErrors.isEmpty()) {
                log.info("订单验证通过");
            } else {
                log.warn("订单验证失败: {}", validationErrors);
            }

            return validationErrors;
        } finally {
            kieSession.dispose();
        }
    }

    /**
     * 执行指定规则
     */
    public Order applySpecificRule(Order order, String ruleName) {
        KieSession kieSession = kieContainer.newKieSession();

        try {
            log.info("执行指定规则: {}", ruleName);

            kieSession.insert(order);

            // 执行指定的规则
            kieSession.getAgenda().getAgendaGroup(ruleName).setFocus();
            kieSession.fireAllRules();

            return order;
        } finally {
            kieSession.dispose();
        }
    }

    /**
     * 批量处理订单
     */
    public List<Order> batchProcessOrders(List<Order> orders) {
        KieSession kieSession = kieContainer.newKieSession();

        try {
            log.info("批量处理 {} 个订单", orders.size());

            // 插入所有订单
            orders.forEach(kieSession::insert);

            // 触发所有规则
            int firedRules = kieSession.fireAllRules();
            log.info("总共执行了 {} 条规则", firedRules);

            return orders;
        } finally {
            kieSession.dispose();
        }
    }
}

6.2 订单处理服务

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

import com.example.drools.model.*;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;

import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.List;

@Slf4j
@Service
public class OrderService {

    private final RuleService ruleService;

    public OrderService(RuleService ruleService) {
        this.ruleService = ruleService;
    }

    /**
     * 创建订单并应用规则
     */
    public Order createOrder(Customer customer, List<OrderItem> items, String promotionCode) {
        // 创建订单
        Order order = new Order();
        order.setOrderId(System.currentTimeMillis());
        order.setCustomer(customer);
        order.setItems(items);
        order.setOrderTime(LocalDateTime.now());
        order.setPromotionCode(promotionCode);
        order.setStatus("CREATED");

        // 计算小计
        items.forEach(OrderItem::calculateSubtotal);

        // 计算总金额
        order.calculateTotalAmount();
        order.setFinalAmount(order.getTotalAmount());

        log.info("创建订单: {}, 客户: {}, 原始金额: {}",
                order.getOrderId(),
                customer.getName(),
                order.getTotalAmount());

        // 验证订单
        List<String> errors = ruleService.validateOrder(order);
        if (!errors.isEmpty()) {
            order.setStatus("VALIDATION_FAILED");
            log.error("订单验证失败: {}", errors);
            return order;
        }

        // 应用折扣规则
        order = ruleService.applyDiscountRules(order);
        order.setStatus("CONFIRMED");

        log.info("订单处理完成: {}, 最终金额: {}, 节省: {}",
                order.getOrderId(),
                order.getFinalAmount(),
                order.getDiscountAmount());

        return order;
    }

    /**
     * 计算订单优惠预览
     */
    public Order previewDiscount(Order order) {
        // 创建订单副本用于预览
        Order previewOrder = cloneOrder(order);
        return ruleService.applyDiscountRules(previewOrder);
    }

    private Order cloneOrder(Order order) {
        Order clone = new Order();
        clone.setOrderId(order.getOrderId());
        clone.setCustomer(order.getCustomer());
        clone.setItems(order.getItems());
        clone.setTotalAmount(order.getTotalAmount());
        clone.setFinalAmount(order.getTotalAmount());
        clone.setPromotionCode(order.getPromotionCode());
        return clone;
    }
}

七、实际应用场景

7.1 电商促销系统

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

import com.example.drools.model.*;
import com.example.drools.service.OrderService;
import org.springframework.stereotype.Component;

import java.math.BigDecimal;
import java.time.LocalDate;
import java.util.Arrays;
import java.util.List;

@Component
public class EcommerceScenario {

    private final OrderService orderService;

    public EcommerceScenario(OrderService orderService) {
        this.orderService = orderService;
    }

    /**
     * 场景1: VIP客户购买电子产品
     */
    public void vipCustomerPurchase() {
        // 创建VIP客户
        Customer vipCustomer = new Customer(
                1L,
                "张三",
                "VIP",
                5000,
                LocalDate.of(2020, 1, 1),
                false
        );

        // 创建商品
        Product laptop = new Product(
                101L,
                "笔记本电脑",
                "ELECTRONICS",
                new BigDecimal("5999"),
                true,
                50
        );

        Product mouse = new Product(
                102L,
                "无线鼠标",
                "ELECTRONICS",
                new BigDecimal("199"),
                true,
                100
        );

        // 创建订单项
        OrderItem item1 = new OrderItem(laptop, 1, laptop.getPrice(), BigDecimal.ZERO);
        OrderItem item2 = new OrderItem(mouse, 2, mouse.getPrice(), BigDecimal.ZERO);

        List<OrderItem> items = Arrays.asList(item1, item2);

        // 处理订单
        Order order = orderService.createOrder(vipCustomer, items, null);

        printOrderSummary(order);
    }

    /**
     * 场景2: 新客户首单
     */
    public void newCustomerFirstOrder() {
        Customer newCustomer = new Customer(
                2L,
                "李四",
                "NORMAL",
                0,
                LocalDate.now(),
                true
        );

        Product book = new Product(
                201L,
                "Java编程思想",
                "BOOKS",
                new BigDecimal("89"),
                false,
                200
        );

        OrderItem item = new OrderItem(book, 3, book.getPrice(), BigDecimal.ZERO);

        Order order = orderService.createOrder(newCustomer, Arrays.asList(item), null);

        printOrderSummary(order);
    }

    /**
     * 场景3: 促销码+大额订单
     */
    public void promotionCodePurchase() {
        Customer goldCustomer = new Customer(
                3L,
                "王五",
                "GOLD",
                2000,
                LocalDate.of(2022, 6, 1),
                false
        );

        Product phone = new Product(
                301L,
                "智能手机",
                "ELECTRONICS",
                new BigDecimal("3999"),
                true,
                30
        );

        OrderItem item = new OrderItem(phone, 1, phone.getPrice(), BigDecimal.ZERO);

        Order order = orderService.createOrder(
                goldCustomer,
                Arrays.asList(item),
                "SAVE20"
        );

        printOrderSummary(order);
    }

    private void printOrderSummary(Order order) {
        System.out.println("\n========== 订单摘要 ==========");
        System.out.println("订单ID: " + order.getOrderId());
        System.out.println("客户: " + order.getCustomer().getName() +
                         " (" + order.getCustomer().getLevel() + ")");
        System.out.println("商品数量: " + order.getItemCount());
        System.out.println("原始金额: ¥" + order.getTotalAmount());
        System.out.println("优惠金额: ¥" + order.getDiscountAmount());
        System.out.println("最终金额: ¥" + order.getFinalAmount());
        System.out.println("状态: " + order.getStatus());
        System.out.println("=============================\n");
    }
}

7.2 规则执行流程

lua 复制代码
订单处理流程:

+------------------+
|   创建订单对象    |
+--------+---------+
         |
         v
+--------+---------+
|   计算商品小计    |
+--------+---------+
         |
         v
+--------+---------+
|   计算订单总额    |
+--------+---------+
         |
         v
+--------+---------+
|   订单验证规则    |
|   - 库存检查     |
|   - 金额检查     |
|   - 权限检查     |
+--------+---------+
         |
    +----+----+
    |         |
   YES       NO
    |         |
    v         v
+---+----+ +--+------------+
|折扣规则 | | 返回验证错误  |
|执行    | +--------------+
+---+----+
    |
    v
+---+------------+
| 按优先级执行:   |
| 1. VIP折扣     |
| 2. 会员折扣     |
| 3. 大额折扣     |
| 4. 促销折扣     |
+---+------------+
    |
    v
+---+------------+
| 计算最终金额    |
+---+------------+
    |
    v
+---+------------+
| 返回订单结果    |
+----------------+

八、Decision Table决策表

8.1 Excel决策表

java 复制代码
// 决策表结构 (在Excel中)
/*
RuleTable DiscountDecisionTable

CONDITION                    CONDITION           CONDITION       ACTION
客户等级                      订单金额            商品数量         折扣率
customer.level              totalAmount         itemCount       discountRate

VIP                         >1000               >5              0.20
VIP                         >500                >0              0.15
GOLD                        >1000               >5              0.15
GOLD                        >500                >0              0.10
SILVER                      >1000               >10             0.10
SILVER                      >500                >0              0.08
NORMAL                      >2000               >20             0.08
NORMAL                      >1000               >10             0.05
*/

8.2 加载Decision Table

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

import org.drools.decisiontable.InputType;
import org.drools.decisiontable.SpreadsheetCompiler;
import org.kie.api.KieServices;
import org.kie.api.builder.KieBuilder;
import org.kie.api.builder.KieFileSystem;
import org.kie.api.builder.KieModule;
import org.kie.api.io.ResourceType;
import org.kie.api.runtime.KieContainer;
import org.kie.internal.io.ResourceFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.io.InputStream;

@Configuration
public class DecisionTableConfig {

    @Bean
    public KieContainer decisionTableContainer() {
        KieServices kieServices = KieServices.Factory.get();
        KieFileSystem kieFileSystem = kieServices.newKieFileSystem();

        // 编译Excel决策表
        InputStream template = getClass()
                .getResourceAsStream("/decision-tables/discount-table.xls");

        SpreadsheetCompiler compiler = new SpreadsheetCompiler();
        String drl = compiler.compile(template, InputType.XLS);

        kieFileSystem.write("src/main/resources/discount-table.drl", drl);

        KieBuilder kieBuilder = kieServices.newKieBuilder(kieFileSystem);
        kieBuilder.buildAll();

        KieModule kieModule = kieBuilder.getKieModule();
        return kieServices.newKieContainer(kieModule.getReleaseId());
    }
}

九、监控与调试

9.1 规则执行监听器

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

import lombok.extern.slf4j.Slf4j;
import org.kie.api.event.rule.*;

@Slf4j
public class RuleExecutionListener extends DefaultAgendaEventListener {

    @Override
    public void beforeMatchFired(BeforeMatchFiredEvent event) {
        String ruleName = event.getMatch().getRule().getName();
        log.info("准备执行规则: {}", ruleName);
    }

    @Override
    public void afterMatchFired(AfterMatchFiredEvent event) {
        String ruleName = event.getMatch().getRule().getName();
        log.info("规则执行完成: {}", ruleName);
    }

    @Override
    public void matchCreated(MatchCreatedEvent event) {
        String ruleName = event.getMatch().getRule().getName();
        log.debug("规则匹配成功: {}", ruleName);
    }

    @Override
    public void matchCancelled(MatchCancelledEvent event) {
        String ruleName = event.getMatch().getRule().getName();
        log.debug("规则匹配取消: {}", ruleName);
    }
}

9.2 使用监听器

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

import com.example.drools.listener.RuleExecutionListener;
import com.example.drools.model.Order;
import org.kie.api.runtime.KieContainer;
import org.kie.api.runtime.KieSession;
import org.springframework.stereotype.Service;

@Service
public class MonitoredRuleService {

    private final KieContainer kieContainer;

    public MonitoredRuleService(KieContainer kieContainer) {
        this.kieContainer = kieContainer;
    }

    public Order executeWithMonitoring(Order order) {
        KieSession kieSession = kieContainer.newKieSession();

        try {
            // 添加监听器
            kieSession.addEventListener(new RuleExecutionListener());

            // 执行规则
            kieSession.insert(order);
            kieSession.fireAllRules();

            return order;
        } finally {
            kieSession.dispose();
        }
    }
}

十、性能优化

10.1 规则优化建议

java 复制代码
// 优化前 - 性能较差
rule "Bad Performance Rule"
    when
        $order: Order()
        $customer: Customer() from $order.customer
        $item: OrderItem() from $order.items
    then
        // 复杂计算
end

// 优化后 - 性能更好
rule "Good Performance Rule"
    salience 100
    when
        $order: Order(
            customer.level == "VIP",
            totalAmount > 1000
        )
    then
        // 简化逻辑
        $order.applyDiscount($order.getTotalAmount().multiply(new BigDecimal("0.15")));
end

10.2 KieSession管理

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

import org.kie.api.runtime.KieContainer;
import org.kie.api.runtime.KieSession;
import org.springframework.stereotype.Component;

import javax.annotation.PreDestroy;
import java.util.concurrent.ConcurrentLinkedQueue;

@Component
public class KieSessionPool {

    private final KieContainer kieContainer;
    private final ConcurrentLinkedQueue<KieSession> sessionPool;
    private static final int POOL_SIZE = 10;

    public KieSessionPool(KieContainer kieContainer) {
        this.kieContainer = kieContainer;
        this.sessionPool = new ConcurrentLinkedQueue<>();
        initializePool();
    }

    private void initializePool() {
        for (int i = 0; i < POOL_SIZE; i++) {
            sessionPool.offer(kieContainer.newKieSession());
        }
    }

    public KieSession getSession() {
        KieSession session = sessionPool.poll();
        if (session == null) {
            return kieContainer.newKieSession();
        }
        return session;
    }

    public void returnSession(KieSession session) {
        if (session != null) {
            session.dispose();
            sessionPool.offer(kieContainer.newKieSession());
        }
    }

    @PreDestroy
    public void cleanup() {
        sessionPool.forEach(KieSession::dispose);
    }
}

十一、测试用例

11.1 单元测试

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

import com.example.drools.model.*;
import com.example.drools.service.RuleService;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

import java.math.BigDecimal;
import java.time.LocalDate;
import java.util.Arrays;
import java.util.List;

import static org.junit.jupiter.api.Assertions.*;

@SpringBootTest
class DroolsRuleTest {

    @Autowired
    private RuleService ruleService;

    private Order testOrder;
    private Customer vipCustomer;

    @BeforeEach
    void setUp() {
        vipCustomer = new Customer(
                1L, "测试VIP", "VIP", 5000,
                LocalDate.of(2020, 1, 1), false
        );

        Product product = new Product(
                1L, "测试商品", "ELECTRONICS",
                new BigDecimal("1000"), true, 100
        );

        OrderItem item = new OrderItem(
                product, 2, product.getPrice(), BigDecimal.ZERO
        );
        item.calculateSubtotal();

        testOrder = new Order();
        testOrder.setOrderId(1L);
        testOrder.setCustomer(vipCustomer);
        testOrder.setItems(Arrays.asList(item));
        testOrder.calculateTotalAmount();
        testOrder.setFinalAmount(testOrder.getTotalAmount());
    }

    @Test
    void testVipDiscount() {
        Order result = ruleService.applyDiscountRules(testOrder);

        assertTrue(result.getDiscountAmount().compareTo(BigDecimal.ZERO) > 0,
                "VIP客户应该有折扣");
        assertTrue(result.getFinalAmount().compareTo(result.getTotalAmount()) < 0,
                "最终金额应该小于原始金额");
    }

    @Test
    void testLargeOrderDiscount() {
        Product expensiveProduct = new Product(
                2L, "昂贵商品", "ELECTRONICS",
                new BigDecimal("1500"), true, 50
        );
        OrderItem item = new OrderItem(
                expensiveProduct, 1, expensiveProduct.getPrice(), BigDecimal.ZERO
        );
        item.calculateSubtotal();

        testOrder.setItems(Arrays.asList(item));
        testOrder.calculateTotalAmount();

        Order result = ruleService.applyDiscountRules(testOrder);

        assertTrue(result.getDiscountAmount().compareTo(BigDecimal.ZERO) > 0,
                "大额订单应该有折扣");
    }

    @Test
    void testOrderValidation() {
        Product outOfStock = new Product(
                3L, "缺货商品", "ELECTRONICS",
                new BigDecimal("500"), true, 0
        );
        OrderItem item = new OrderItem(outOfStock, 1, outOfStock.getPrice(), BigDecimal.ZERO);

        testOrder.setItems(Arrays.asList(item));

        List<String> errors = ruleService.validateOrder(testOrder);

        assertFalse(errors.isEmpty(), "应该有验证错误");
        assertTrue(errors.stream().anyMatch(e -> e.contains("库存")),
                "应该包含库存错误");
    }
}

十二、总结

本文全面介绍了Drools规则引擎的实战应用:

  1. 核心架构:KieBase、KieSession、Agenda工作原理
  2. 规则编写:DRL语法、条件匹配、动作执行
  3. Spring集成:配置管理、依赖注入
  4. 实战场景:电商促销、订单折扣、库存验证
  5. 高级特性:Decision Table、规则监听、性能优化
  6. 最佳实践:规则设计、Session管理、测试策略

Drools将业务规则与代码解耦,使规则更易维护和扩展,是企业级应用的理想选择。

相关推荐
曹牧1 小时前
Java:@SuppressWarnings
java·开发语言
她说..1 小时前
Spring Boot中读取配置文件的5种方式汇总
java·spring boot·后端·spring·springboot
ChrisitineTX1 小时前
双 11 预演:系统吞吐量跌至 0!一次由 Log4j 锁竞争引发的线程“集体猝死”
java·log4j
薛纪克1 小时前
Lambda Query:让微软Dataverse查询像“说话”一样简单
java·spring·microsoft·lambda·dataverse
程序员-周李斌1 小时前
CopyOnWriteArrayList 源码分析
java·开发语言·哈希算法·散列表
廋到被风吹走1 小时前
【Spring】两大核心基石 IoC和 AOP
java·spring
明有所思1 小时前
springsecurity更换加密方式
java·spring
却话巴山夜雨时i1 小时前
295. 数据流的中位数【困难】
java·服务器·前端