easy-rules 规则引擎组件设计
本文档描述 easy-rules 组件的设计与实现。该组件提供多规则引擎统一抽象层,通过
EngineExecutor顶层基类衔接easy-storeConfigStorage 配置存储,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 的关系
| 维度 | Drools | Easy-Rules | Matrix easy-rules |
|---|---|---|---|
| 复杂度 | 高(RETE 算法、规则语言 DR L) | 低(Java POJO + MVEL 表达式) | 低(Easy-Rules + LiteFlow) |
| 依赖 | drools-core + drools-compiler | easy-rules-mvel/jexl | easy-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/脚本"]
引擎选择映射:
| type | implementation | → 类 |
|---|---|---|
rules | mvel | MVELRuleExecutor ✅ |
rules | liteflow | LiteFlowExecutor ✅ |
state-machine / workflow | — | TODO,返回 null 跳过 |
存储选择映射:
| repositoryType | → 类 |
|---|---|
in-memory / memory | InMemoryRepository |
file | FileRepository |
redis | RedisRepository |
四、核心 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-Rules | LiteFlow 自动加载 XML/脚本 | createRules() 抛 UnsupportedOperationException |
| Facts key | head/req/resp | 直接传 request + context bean | head/in/out(与 MVEL 不一致) |
| 引擎 | Easy-Rules + MVEL | LiteFlow FlowExecutor | Easy-Rules + JEXL |
| 配置注册 | EngineRegistryPostProcessor ✅ | EngineRegistryPostProcessor ✅ | EngineRegistryPostProcessor ❌ 无分支 |
| 内置 Util | util.format/copy/move | 无(用 LiteFlow 组件) | 无 |
| 规则格式 | JSON / YAML | XML / 脚本 | JSON / YAML |
| 依赖 scope | compile | optional | optional |
MVEL Facts 映射(规则脚本可访问的变量):
| Easy-Rules Facts key | RulesFacts 字段 | 说明 |
|---|---|---|
"head" | header | 请求头 Map |
"req" | request | 请求体对象 |
"resp" | result | 执行结果(规则可读写) |
"util" | 内置 Util | format / 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 动态 Bean | BeanDefinitionRegistryPostProcessor 在 Spring 实例化前批量注册 | 支持多实例、多存储组合;不依赖显式 @Bean 方法 |
| RulesExecutor.execute 内部 catch | 不向上抛,包装 RulesException 写入 facts.result | 规则执行失败不应中断整个调用链 |
| JEXL/LiteFlow 都是 optional | compile scope 只有 easy-rules-mvel | easy-rules 可作为纯 MVEL 引擎独立使用 |
| LiteFlow createRules 抛 UnsupportedOperationException | LiteFlow 自己管理规则加载(XML/脚本),不需要 ConfigStorage.loadAttachment | 架构旁路但不强行统一 |