一佳技术

easy-rules 规则引擎组件设计

本文档描述 easy-rules 组件的设计与实现。该组件提供多规则引擎统一抽象层,通过 EngineExecutor 顶层基类衔接 easy-store ConfigStorage 配置存储,RulesExecutor<R> 中间层封装规则执行 API,三个引擎实现(MVEL 完整生产级 / LiteFlow 可用但绕开 ConfigStorage / JEXL 骨架半成品)。EngineRegistryPostProcessor 实现动态 Bean 注册——启动时根据 easy.engines.instances 配置批量创建引擎实例 + 存储实例。


一、背景与问题

1.1 为什么需要规则引擎

Matrix Framework 多个组件需要可配置的条件判断:

场景引擎
网关连接器路由规则(按用户类型/地区/时段选择协议)MVEL
价格计算规则(阶梯价/促销价/会员价)MVEL
工作流节点跳转条件LiteFlow 链路编排

业务方希望规则以文本形式持久化(Redis / 文件 / DB),无需改代码重启即可调整。

1.2 与 Drools / Easy-Rules 的关系

维度DroolsEasy-RulesMatrix easy-rules
复杂度高(RETE 算法、规则语言 DR L)低(Java POJO + MVEL 表达式)低(Easy-Rules + LiteFlow)
依赖drools-core + drools-compilereasy-rules-mvel/jexleasy-rules-mvel + liteflow-starter
规则存储内置 KnowledgeBase外部文件easy-store ConfigStorage(统一接口)
编排能力无无(单规则)LiteFlow(链路编排)

easy-rules 选择 Easy-Rules + MVEL 做单规则、LiteFlow 做链路编排,避开 Drools 的复杂性,同时提供统一抽象层让上层不感知引擎差异。


二、组件定位与边界

定位:规则执行能力门面 + 引擎动态注册工厂。业务方通过 EngineRegistryPostProcessor 配置批量创建引擎实例,通过 RulesExecutor.execute(chainId, facts) 触发执行。

边界:

做不做
EngineExecutor 顶层 → RulesExecutor 中间层 → 引擎实现三层抽象不做规则版本管理/回滚
MVEL / JEXL / LiteFlow 三引擎实现不做规则可视化编辑器
RulesFacts 上下文容器不做规则热加载(改规则文本需重启应用)
EngineRegistryPostProcessor 动态 Bean 注册不做规则持久化(靠 easy-store)
RulesLoader 规则文本 → 引擎对象转换不替代 Easy-Rules / LiteFlow 原生 API

约束:

  • JEXL 引擎骨架半成品:createRules() 抛 UnsupportedOperationException,无法实际加载规则;EngineRegistryPostProcessor 无 "jexl" 分支
  • LiteFlow 绕开 easy-store:走 LiteFlow 自己的规则加载机制(XML/脚本文件),createRules() 同样抛 UnsupportedOperationException
  • MVEL 是唯一完整走 ConfigStorage → RulesLoader 的引擎
  • Facts key 不统一:MVEL 用 head/req/resp,JEXL 用 head/in/out
  • 所有引擎依赖均为 optional scope(easy-rules-mvel 除外,它是 compile)

三、总体架构

flowchart TD
    subgraph "引擎抽象层"
        A1["EngineExecutor (abstract, io.matrix.flows)<br/>持有 ConfigStorage<ConfigDefinition><br/>提供 saveConfig/loadConfig/deleteConfig"]
        A2["RulesExecutor<R> (abstract, io.matrix.rules)<br/>继承 EngineExecutor<br/>fire/check/shouldBeEvaluate/createRules"]
    end

    subgraph "三引擎实现"
        B1["MVELRuleExecutor<br/>extends RulesExecutor<Rules><br/>⭐ 完整生产级<br/>ConfigStorage → MVELRulesLoader → Easy-Rules"]
        B2["JEXLRuleExecutor<br/>extends RulesExecutor<Rules><br/>⚠️ 骨架半成品<br/>createRules 抛 UnsupportedOperationException"]
        B3["LiteFlowExecutor<br/>extends RulesExecutor<br/>extends InitializingBean<br/>⚠️ 绕开 ConfigStorage<br/>FlowExecutor.execute2Resp()"]
    end

    subgraph "规则存储(easy-store)"
        C1["ConfigStorage<ConfigDefinition>"]
        C2["InMemoryRepository / FileRepository / RedisRepository"]
    end

    subgraph "动态 Bean 注册"
        D1["EngineProperties (prefix=easy.engines)<br/>instances[] + storages[]"]
        D2["EngineRegistryPostProcessor<br/>BeanDefinitionRegistryPostProcessor<br/>determineEngineClass + determineRepositoryClass<br/>批量创建引擎 Bean + 存储 Bean"]
    end

    subgraph "规则执行"
        E1["RulesFacts<br/>context + header + request + result"]
        E2["execute(chainId, param)<br/>→ fire(chainId, facts)<br/>→ 包装 Result<T>"]
    end

    A2 --> B1 & B2 & B3
    A1 --> C1
    C1 --> C2
    D2 -->|"读取"| D1
    D2 -->|"创建引擎 Bean"| B1 & B2 & B3
    D2 -->|"创建存储 Bean"| C1
    B1 -->|"ConfigStorage.loadAttachment"| C1
    B3 -.->|"不走 ConfigStorage"| LF["LiteFlow FlowExecutor<br/>自动加载 XML/脚本"]

引擎选择映射:

typeimplementation→ 类
rulesmvelMVELRuleExecutor ✅
rulesliteflowLiteFlowExecutor ✅
state-machine / workflow—TODO,返回 null 跳过

存储选择映射:

repositoryType→ 类
in-memory / memoryInMemoryRepository
fileFileRepository
redisRedisRepository

四、核心 API 草图

EngineExecutor(顶层抽象,io.matrix.flows 包)

// 持有 ConfigStorage + namespace + version 字段
protected ConfigStorage<ConfigDefinition> configStorage;
protected String namespace;

// 配置 CRUD(走 easy-store)
void saveConfig(String namespace, String ruleId, ConfigDefinition config);
Optional<ConfigDefinition> loadConfig(String namespace, String ruleId);
void deleteConfig(String namespace, String ruleId);
boolean existsConfig(String namespace, String ruleId);

RulesExecutor<R>(中间层,io.matrix.rules 包)

abstract class RulesExecutor<R> extends EngineExecutor {
    // 抽象:触发规则链执行
    abstract void fire(String chainId, RulesFacts facts);

    // 抽象:ConfigDefinition.getText() → 引擎特定规则对象
    protected abstract R createRules(ConfigDefinition definition);

    // 默认:加载并转换规则(走 ConfigStorage.loadAttachment)
    protected R loadRules(String chainId);

    // 完整入口:自动构造 RulesFacts,捕获异常写入 facts.result
    <T> Result<T> execute(String chainId, Map<String,Object> header, Object param, Map<String,Object> context);

    // 便捷入口
    <T> Result<T> execute(String chainId, Object param);

    // 严格执行:失败抛 RulesException
    <T> T evaluate(String chainId, Map<String,Object> header, Object body, Map<String,Object> context);

    // 检查每条规则是否应执行
    Map<String, Boolean> check(String chainId, RulesFacts facts);
}

RulesFacts(上下文容器)

// 规则执行时 Facts,自动构造贯穿整个规则链
private Map<String, Object> context = new HashMap<>();   // 用户/引擎共享上下文
private Map<String, Object> header;                       // 请求头
private Object request;                                  // 请求体
private Result result = new Result();                     // 执行结果(可被规则读写)

static RulesFacts of(request) / of(header, request) / of(header, request, context)
void setException(RulesException e);   // execute 内部 catch 后写入
void setData(Object data);             // 规则执行后写入

RulesLoader<R>(@FunctionalInterface)

@FunctionalInterface
public interface RulesLoader<R> {
    R create(String text);   // 规则文本 → 引擎规则对象
}

五、三引擎差异

维度MVELRuleExecutor ✅LiteFlowExecutor ✅JEXLRuleExecutor ⚠️
成熟度完整生产级可用但绕开 ConfigStorage骨架半成品
规则加载ConfigStorage → MVELRulesLoader → Easy-RulesLiteFlow 自动加载 XML/脚本createRules() 抛 UnsupportedOperationException
Facts keyhead/req/resp直接传 request + context beanhead/in/out(与 MVEL 不一致)
引擎Easy-Rules + MVELLiteFlow FlowExecutorEasy-Rules + JEXL
配置注册EngineRegistryPostProcessor ✅EngineRegistryPostProcessor ✅EngineRegistryPostProcessor ❌ 无分支
内置 Utilutil.format/copy/move无(用 LiteFlow 组件)无
规则格式JSON / YAMLXML / 脚本JSON / YAML
依赖 scopecompileoptionaloptional

MVEL Facts 映射(规则脚本可访问的变量):

Easy-Rules Facts keyRulesFacts 字段说明
"head"header请求头 Map
"req"request请求体对象
"resp"result执行结果(规则可读写)
"util"内置 Utilformat / copy / move
context entries各自 key用户传入 context Map

六、动态 Bean 注册

EngineProperties(前缀 easy.engines)

easy:
  engines:
    # 引擎实例(必配)
    instances:
      - beanName: priceRulesEngine
        type: rules
        implementation: mvel
        enabled: true
        namespace: pricing
        repositoryType: file
        properties:
          engine: mvel
    # 独立存储实例(可选,instances 会自动创建 ConfigStorage)
    storages:
      - beanName: flowDefinitionStorage
        repositoryType: file
        properties:
          basePath: /opt/flows/

EngineRegistryPostProcessor 流程

flowchart TD
    A["Spring Boot 启动"] --> B["BeanDefinitionRegistryPostProcessor.postProcessBeanDefinitionRegistry"]
    B --> C["Binder.bind(easy.engines, EngineProperties.class)"]
    C --> D{"有 storages?"}
    D -- "是" --> E["逐个注册 ConfigStorage + Repository BeanDefinition"]
    D -- "否" --> F
    E --> F{"有 instances?"}
    F -- "否" --> Z["跳过"]
    F -- "是" --> G["遍历每个 instance"]
    G --> H["determineEngineClass(type, implementation)<br/>determineRepositoryClass(repositoryType)"]
    H --> I["createConfigStorageBean()<br/>DefaultConfigStorage + Repository"]
    I --> J["创建引擎 BeanDefinition<br/>注入 configStorage + namespace + version"]
    J --> K["Spring 实例化 + 依赖注入"]
    K --> L["引擎 Bean 就绪"]

七、已知限制

限制严重度说明
JEXL 引擎半成品高createRules 抛 UnsupportedOperationException,EngineRegistryPostProcessor 无分支
LiteFlow 绕开 ConfigStorage中走 LiteFlow 自己的规则加载,不走 easy-store
Facts key 不统一中MVEL 用 head/req/resp,JEXL 用 head/in/out
规则修改需重启低ConfigStorage.saveConfig() 后引擎不会自动 loadAttachment
EngineProperties.type 预留未实现低state-machine / workflow 分支返回 null 跳过
Repository 选择无 Redis 完整实现低RedisRepository 半成品(listKeys/delete 不生效)

八、关键设计决策

决策内容理由
三层抽象(EngineExecutor → RulesExecutor → 引擎实现)顶层统一存储、中间统一执行 API、底层引擎差异化为未来扩展 state-machine / workflow 预留口子
ConfigStorage.loadAttachment 懒加载规则文本首次使用时才转换为引擎对象,存入 ConfigDefinition.attachment避免启动时加载所有规则
EngineRegistryPostProcessor 动态 BeanBeanDefinitionRegistryPostProcessor 在 Spring 实例化前批量注册支持多实例、多存储组合;不依赖显式 @Bean 方法
RulesExecutor.execute 内部 catch不向上抛,包装 RulesException 写入 facts.result规则执行失败不应中断整个调用链
JEXL/LiteFlow 都是 optionalcompile scope 只有 easy-rules-mveleasy-rules 可作为纯 MVEL 引擎独立使用
LiteFlow createRules 抛 UnsupportedOperationExceptionLiteFlow 自己管理规则加载(XML/脚本),不需要 ConfigStorage.loadAttachment架构旁路但不强行统一