世界万事万物皆有规则
说起规则引擎, 相信很多小伙伴对于规则引擎产生了很多疑问. 它是什么? 它能做啥? 应该怎么做? 希望通过阅读下面的内容能给你一些启发.
首先规则引擎是什么,我们来看下百度百科是怎么定义的
规则引擎由推理引擎发展而来,是一种嵌入在应用程序中的组件,实现了将业务决策从应用程序代码中分离出来,并使用预定义的语义模块编写业务决策。接受数据输入,解释业务规则,并根据业务规则做出业务决策。
上面说的很清晰, 总结一句话规则引擎做的事情就是 录入特定判断逻辑, 通过输入的数据进行决策
首先我们有个基本的优缺点分析:
规则引擎带来的优点:
规则引擎带来的缺点:
规则引擎/指标 | drools | gengine |
---|---|---|
上手难度 | 有一定门槛 | 易 |
运行方式 | 仅支持顺序型 | 支持顺序/并行/N-M等模式 |
开发语言 | java | golang |
社区活跃度 | 高 | 一般 |
基于本人水平, 此处选择了更易学习的 gengine 作为研究对象 (虽然规则引擎有不同的运行模式和语言, 但对于我们理解本质并没什么区别)
支持部分规则模式
运行模式 | 方法名 | 含义 |
---|---|---|
顺序型 | ExecuteSelectedRulesWithControlAndStopTag | 按规则优先级执行(从上往下)-耗时为所有规则执行时间累加 |
并发型 | ExecuteSelectedRulesConcurrent | 所有规则并发执行(执行时间为执行时间最长的-考虑池限制) |
混合模式 | ExecuteSelectedRulesMixModel | 先执行一个优先级最高的规则,然后并发执行剩下的所有规则 |
N-M | ExecuteNConcurrentMConcurrent | 前N个规则并发执行, M个规则也并发执行/ 前N个规则顺序执行, M个规则并发执行 |
制定规则中依赖的方法(硬编码)
type ruleResponse struct {
Code int
Data map[string]interface{}
}
func success(code int) ruleResponse {
return ruleResponse{
Code: code,
Data: make(map[string]interface{}),
}
}
func (rule ruleResponse) AddDataParam(name string, value interface{}) {
rule.Data[name] = value
}
规则中是不支持自定义函数和结构体的, 当需要返回非int、字符串、bool值的时候, 需要我们外部注入对应实现方法, 供规则内调用 (例如自定义结构体、自定义复杂规则验证、获取订单课程等方法)
声明规则
rule1 := `
rule "case0" "case1测试用例" salience 0 //后执行
begin
Print(flag-2)
successObj := success(0)
successObj.AddDataParam("name0","test 000 value")
return successObj
end
rule "case1" "case1测试用例" salience 1 //后执行
begin
a = 8
if a < 1 {
println("a < 1")
} else if a >= 1 && a <6 {
println("a >= 1 && a <6")
} else if a >= 6 && a < 7 {
println("a >= 6 && a < 7")
} else if a >= 7 && a < 10 {
println("a >=7 && a < 10")
} else {
println("a > 10")
}
end
rule "case2" "case2测试用例" salience 2 //先执行
begin
if flag>0 {
Print(flag+3)
stag.StopTag = true
successObj := success(222)
successObj.AddDataParam("name2","test value")
return successObj
}
end
`
上图中声明了规则, Print、success是我们外部注入的方法, 我们将在下一步制定. rule表示一段新的规则开始 , 第一个为规则名(返回结果时候为对应key), 第二个为描述, 第三个 salience 表示规则的优先级 规则的优先级数字越大优先级越高(当使用 AsGivenSortedName 方法时, 会忽略掉规则内优先级作用 )
注入规则内依赖方法, 初始化规则池
apis := make(map[string]interface{})
apis["success"] = success
apis["Print"] = fmt.Println
var poolMinLen int64 = 50
var poolMaxLen int64 = 100
pool, err := engine.NewGenginePool(poolMinLen, poolMaxLen, engine.SortModel, rule1, apis)
if err != nil {
fmt.Println(err.Error())
return
}
第一个参数 poolMinLen 表示初始化池最小50 第二个参数 poolMaxLen 表示初始化池最大100 第三个参数为设置执行模式(分为顺序执行、并发执行等), 只有调用 ExecuteSelectedRules 和 ExecuteSelectedRulesConcurrent 有效 (后续可做规则模式选择) 第四个参数是我们配置的规则 第五个是我们注入的变量值、方法等
调用执行规则
data := make(map[string]interface{})
data["flag"] = 2
stag := &engine.Stag{StopTag: false}
data["stag"] = stag //显示将标记注入进去
//_ = pool.RemoveRules([]string{"case1"}) //移除规则
/*_ = pool.UpdatePooledRulesIncremental(` //新增规则
rule "case66" "case66测试用例" salience 3
begin
Print("case66")
return false
end
`)*/
_, resp := pool.ExecuteSelectedRulesWithControlAndStopTag(data, true, stag, []string{"case1", "case66", "case2", "case0"})
fmt.Println(resp)
//output:
//5
//map[case2:{222 map[name2:test value]}]
业务调用方变量注入, StopTag 声明为可中断, 执行选定的规则
层级架构图
image.png
业务场景通过业务编号(标识)调用规则接入层, 根据运行模式运行关联的规则. 接入层负责根据业务编号拿到具体规则进行运行, 同时接入层负责收集业务执行的异常与结果 最底层规则信息管理部分, 负责规则信息数据的维护与整理
业务接入模块分工流程
image.png
随着业务的快速发展, 代码的生命周期越来越短, 项目慢慢发展成为恐怖的“巨兽”, 吞噬着研发同学的耐心, 叫苦不迭却难以破局, 上面讲了这么多, 我们怎么来判断是否适合引入规则引擎呢?
首先达成共识, 领导及产研同事是认可当下值得去做的, 可以解决掉我们目前发现的痛点 (发现痛点可以先通过 最简单的方式先找出那些类似 if else多分支决策场景, 根据历史改动及业务需求来进行判断), 当然很多时候痛点习惯了就不痛了, 这就需要仔细思考了。
当然最好的话, 你对代码所服务的业务具有很好的预见能力.
http://www.bstek.com/#videos
https://rencalo770.github.io/gengine_doc/#/introduce
https://github.com/bilibili/gengine
扫码关注腾讯云开发者
领取腾讯云代金券
Copyright © 2013 - 2025 Tencent Cloud. All Rights Reserved. 腾讯云 版权所有
深圳市腾讯计算机系统有限公司 ICP备案/许可证号:粤B2-20090059 深公网安备号 44030502008569
腾讯云计算(北京)有限责任公司 京ICP证150476号 | 京ICP备11018762号 | 京公网安备号11010802020287
Copyright © 2013 - 2025 Tencent Cloud.
All Rights Reserved. 腾讯云 版权所有