Slickflow 规则集与 NRules 集成实践:从设计到产品落地
Slickflow 规则集与 NRules 集成实践:从设计到产品落地
1. 背景与目标
工作流引擎中的 Business Rule Task(规则任务) 需要把「业务规则」从流程图里抽离出来:规则可复用、可版本化、可独立测试,而 BPMN 只负责 选哪套规则 与 把流程变量交给规则。
本实践在 Slickflow 中落地 规则集(Rule Set),并集成开源规则引擎 NRules,同时支持 程序集规则(强类型 C# Rule) 与 JSON DSL 规则(声明式 when/outputs) 两种形态,统一落在数据库表 wf_rule_set,由 RuleSetExecutionManager 按模式调度执行。
2. 总体架构:数据、引擎、建模三层解耦
| 层级 | 职责 |
|---|---|
仓储 wf_rule_set |
规则集编码、名称、mode、rule_content、启用状态等 |
执行 RuleSetExecutionManager |
按 mode 解析 rule_content:NRules 或 JSON DSL |
| 流程建模 BPMN 扩展 | RuleTask 上只绑定 ruleSetCode;运行时从库加载完整定义 |
这样 规则本体 与 流程定义 XML 解耦:同一套规则可被多个流程/节点引用;更新规则主要改表数据或发布流程版本,而不必反复改 BPMN 里的规则正文。
2.1 请假流程:规则任务与排他网关(文字版流程图)
下面用 请假业务 把「规则集算出的结果如何驱动 BPMN」说清楚:流程里只有 一套规则集(例如绑定 LeaveApproveRule 或等价的 DSL),规则内部按 三种互斥情形 写出 ApprovalLevel;排他网关 再按该变量走 三条出线,分别对应三种审批路径——这与 LeaveApproveRule 中 Leader / Manager / HR 三档一致。
参与者与变量约定: 员工提交请假时,流程中已有(或经表单写入)LeaveDays(天数)、LeaveType(类型,如事假、病假等)。规则任务执行前,这些量作为规则输入;执行后,规则向流程变量写入 ApprovalLevel,供网关条件引用。

节点顺序(自上而下、与典型 BPMN 泳道图一致):
- 开始事件 — 流程实例创建,进入请假子流程。
- 用户任务:填写 / 提交请假申请 — 申请人确认天数与类型,数据落入流程变量(及必要时
wf_variable声明的 Input,供规则任务引用)。 - 业务规则任务(Business Rule Task) — 绑定
wf_rule_set中的请假规则集(ruleSetCode)。引擎调用RuleExecutor→RuleSetExecutionManager,根据mode执行 NRules 或 JSON DSL,唯一输出路由键 为ApprovalLevel。 - 排他网关(Exclusive Gateway) — 以
ApprovalLevel为唯一判别依据,三条出线互斥,同一实例只激活其中一条:- 分支一:
ApprovalLevel == "Leader"→ 用户任务「直属领导审批」(短假、常规情形,由组长 / 主管处理)。 - 分支二:
ApprovalLevel == "Manager"→ 用户任务「部门经理审批」(例如天数超过 3 天且未落入 HR 档)。 - 分支三:
ApprovalLevel == "HR"→ 用户任务「人事审批」(例如超过 7 天或病假等需人事备案的情形)。
- 分支一:
- 汇合 — 三条审批任务完成后,经 排他网关或并行汇合结构(依建模习惯二选一)汇聚到同一后续路径(如「通知申请人结果」或「归档」)。
- 结束事件 — 流程完结。
要点: 网关上的三条条件不是再写三套独立「业务规则」,而是 消费规则任务已写好的同一个变量;「三种规则」体现在 规则集内部 对 LeaveDays、LeaveType 的分支判断(NRules 里 ApplyApproval 的三段 if / else if / else,或 DSL 里多条 when),流程图侧则 一规则任务 + 一网关三分支,职责清晰、易测易改。
3. 规则集两种执行模式
3.1 ruleTypes:NRules 程序集规则
rule_content 为 JSON,包含 ruleTypes 数组,元素为 程序集限定名(与 NRules Rule 派生类一致)。
运行时:
RuleRepository加载类型并编译会话;- 注入
RuleInputFact(变量字典)与RuleOutputFact(输出字典); session.Fire()执行规则,从输出字典取结果(如ApprovalLevel)。
适合:复杂分支、需强类型与单测、与现有 .NET 业务规则类复用。
下面是与「请假审批级别」对应的 NRules 规则类 完整示例(引擎通过 ruleTypes 中的程序集限定名加载该类;RuleInputFact / RuleOutputFact 由引擎注入,变量从 input.Vars 读取,结果写入 output):
源码位置: sfbpmn-project/core/Slickflow.Module.BusinessRule/Approval/LeaveApproveRule.cs
using NRules.Fluent.Dsl;
using Slickflow.Engine.Business.Entity;
using System.Globalization;
namespace Slickflow.Module.BusinessRule.Approval
{
public class LeaveApproveRule : Rule
{
public override void Define()
{
RuleInputFact input = null!;
RuleOutputFact output = null!;
When()
.Match(() => input)
.Match(() => output);
Then()
.Do(_ => ApplyApproval(input, output));
}
private static void ApplyApproval(RuleInputFact input, RuleOutputFact output)
{
var days = 0;
var leaveType = string.Empty;
if (input.Vars.TryGetValue("LeaveDays", out var d) && d != null)
{
var rawDays = Convert.ToString(d, CultureInfo.InvariantCulture);
if (!string.IsNullOrWhiteSpace(rawDays)
&& int.TryParse(rawDays, NumberStyles.Integer, CultureInfo.InvariantCulture, out var parsedDays))
{
days = parsedDays;
}
}
if (input.Vars.TryGetValue("LeaveType", out var t) && t != null)
leaveType = t.ToString() ?? string.Empty;
if (days > 7 || leaveType.Equals("Sick", StringComparison.OrdinalIgnoreCase))
output.Set("ApprovalLevel", "HR");
else if (days > 3)
output.Set("ApprovalLevel", "Manager");
else
output.Set("ApprovalLevel", "Leader");
}
}
}
3.2 bindingsJson(表字段名,含义为 JSON 规则 DSL)
此处 mode = bindingsJson 表示 rule_content 内是 JSON DSL,与早期 BPMN 上曾出现的同名「变量绑定」概念已区分;任务节点不再承载大块绑定 JSON,避免与 wf_rule_set.rule_content 混淆。
rule_content 形如:
- 顶层
stopOnFirstMatch与rules数组; - 每条含
name、when(条件表达式)与outputs(输出键值); - 按数组顺序匹配,
stopOnFirstMatch: true时 首条命中即停止。
适合:规则变更频繁、希望少发版、由运营/实施直接改 JSON 的场景。
DSL 模式示例(入库到 wf_rule_set.rule_content,mode 为 bindingsJson;变量名 LeaveDays、LeaveType、ApprovalLevel 与流程/规则输入约定一致即可):
{
"stopOnFirstMatch": true,
"rules": [
{
"name": "ShortSickLeave",
"when": "LeaveDays <= 3 || LeaveType == \"Sick\"",
"outputs": {
"ApprovalLevel": "Leader"
}
},
{
"name": "LongLeave",
"when": "LeaveDays > 3",
"outputs": {
"ApprovalLevel": "Manager"
}
}
]
}
说明:上例与上一节 LeaveApproveRule.cs 的分档逻辑 不必完全一致——前者用声明式条件快速迭代,后者用 C# 实现更细的分支(例如病假、超过 7 天走 HR);产品可按同一业务域分别维护两套实现或逐步对齐语义。
4. 与 Slickflow 产品的集成路径
4.1 引擎侧:RuleExecutor → RuleSetExecutionManager
-
RuleExecutor(Slickflow.Engine)在 RuleTask 执行时:- 用
ruleSetCode从wf_rule_set取实体; - 组装 规则输入字典,调用
RuleSetExecutionManager.Execute; - 将输出写回
wf_process_variable(活动作用域),供后续网关、任务使用。
- 用
-
RuleConfigDetail除 BPMN 中的ruleSetCode外,可在解析流程时MergeFrom合并表字段(名称、描述、rule_content、mode等),便于调试与 API 展示;执行仍以库表为准,避免 BPMN 与库不一致。
4.2 变量输入:流程变量 + wf_variable 定义
规则入参需要与流程数据对齐:
- 流程级 / 活动级
wf_process_variable提供运行时值; wf_variable描述当前活动上的变量定义:方向、是否引用前序节点、source_ref/source_variable_name等。
RuleTask 组装输入时:先尊重 wf_variable 中声明的 Input——引用型输入从前序活动实例的 wf_process_variable 按引用解析;未声明则回退为合并活动变量(兼容旧流程)。声明了输入但暂无值时写入空字符串,避免 JSON DSL 中 LeaveType == "Sick" 等表达式因缺键导致整条规则无法命中。
4.3 模式枚举与 API
- 使用
RuleSetModeEnum(ruleTypes/bindingsJson)与RuleSetModeHelper做持久化字符串与枚举互转,避免散落魔法字符串。 - sfdapi
RuleSetController在保存时校验mode与rule_content形状,保证入库数据可被引擎执行。
4.4 设计器(sfd)
-
Modeling 菜单进入 业务规则设置,维护规则集列表与
rule_content;
![73fcfe68-cb1e-4eb6-8534-b04daee66587]()
-
RuleTask 属性里选择 规则集编码,与引擎读取路径一致。
![5cda614b-fbfc-4d34-9f0c-e008bf5d3f18]()
5. 开发过程回顾:关键决策
- 规则与流程分离:规则集中在
wf_rule_set,BPMN 只存ruleSetCode,降低模型体积与合并冲突。 - 双模共存:NRules 适合工程化规则类;JSON DSL 适合轻量、快速调整;用
mode明确分支,避免隐式猜测。 - 任务侧不再承载「规则绑定 JSON」:变量来源改为流程库表与命名约定,减少重复配置与歧义。
- 空值与 DSL:输入字典对声明变量给默认空串,减少「条件看似正确却永不命中」类问题。
- 运维注意:若流程定义启用内存缓存,更新 XML/规则后需关注缓存与实例版本,避免「库已改、内存仍旧」的错觉。
6. 小结
Slickflow 规则集功能将 NRules 与 自研 JSON DSL 执行器 统一在 RuleSetExecutionManager 之下,通过 wf_rule_set.mode + rule_content 驱动;RuleTask 只负责绑定规则集与喂入流程变量,从而形成清晰的分层:产品(建模/API)→ 引擎(执行与持久化)→ 规则运行时(NRules 或 DSL)。
后续可在同一套扩展点上增加规则版本、审计日志、规则仿真接口等,而不必改动 BPMN 核心结构。
本文基于 Slickflow 仓库中 RuleSetExecutionManager、RuleExecutor、VariableManager、wf_rule_set 等相关实现整理,如有变更以当前代码与数据库脚本为准。


浙公网安备 33010602011771号