基于规则架构风格对业务的重构
-
- 引言
- 基于规则的架构风格
- 常见的规则引擎
-
- Java
- Python
- ZEN-Engine
- 最后
引言
在现代系统开发中,由于业务规则复杂且常常发生变化。例如电商平台针对某次活动实施打折促销,需要根据用户是否为VIP,是否被加入黑名单,购物车的订单金额,商品类型等因素最后计算出用户的打折率。这部分业务逻辑如果硬编码,无论是直接if-else或者采用策略设计模式,后期打折方式更改,都将对源码进行维护。这不符合开闭原则。长远而言,代码将难以维护。
基于规则的架构风格
基于规则的架构风格是解决以上问题的思路之一。将业务规则从业务代码中抽离为独立的规则文件,面对业务规则的改变,只需要修改或者增加业务规则文件,不用重启系统,规则自动更新。
常见的规则引擎
Java
Java技术栈里常见的规则引擎有Drools,Easy Rules。
Drools将业务规则抽象为.drl文件。是Java里常用的规则引擎。Drools虽为Java生态主流,但通过JPype可在Python中调用,适合已有Drools资产的项目集成
Easy Rules 极简API设计,通过Java注解/YAML解耦业务逻辑,与SpringBoot集成良好。
Python
Python技术栈中常见的规则引擎有rule-engine,Pyke,PRISM-Python, zen-engine。
- rule-engine支持类Python语法规则定义,内置正则匹配、日期时间、类型校验等操作符,适合数据过滤和简单业务规则。
- Pyke基于Prolog的逻辑编程引擎,支持前向推理(生成新事实)和后向推理(目标驱动规划),需定义事实(Facts)和规则(Rules)。适用于专家系统、合规性检查等需要复杂逻辑链的场景。
- PRISM-Python从数据集中归纳可解释规则(如“若feat_A=hot且feat_C=round则目标=blue”),支持分箱处理数值特征,输出类似决策树但更简洁。
- ZEN Engine 是一款跨平台的开源业务规则引擎(BRE)。它使用Rust编写,并为NodeJS**、**Python和Go提供原生绑定。ZEN 引擎允许从 JSON 文件加载并执行JSON 决策模型(JDM)。获取JSON的方式可以是文件系统,数据库或者服务调用。ZEN Engine是一款高性能/分布式规则引擎,设计为Drools的现代替代品。接下来将介绍ZEN Engine引擎集成在Python中的实践。
ZEN-Engine
首先安装zen-engine
pip install zen-engine asyncio
demo代码
import asyncio
import zen
"""业务规则:
业务需求:根据用户属性(VIP等级、购物车金额)和商品类别动态计算折扣,规则如下:
基础折扣:购物车金额满 1000 元打 9 折,满 2000 元打 85 折;
VIP叠加折扣:VIP 用户额外享受 5% 折扣(可与基础折扣叠加);
黑名单限制:黑名单用户不享受任何折扣;
商品类别排除:数码类商品不参与折扣活动。
"""
def loader(key):
with open("./jdm_directory/" + key, "r") as f:
return f.read()
async def main():
engine = zen.ZenEngine({"loader": loader})
# 测试用例1: VIP用户+非数码商品+高金额
input1 = {
"user": {"isVIP": True, "isBlacklisted": False},
"cart": {
"total": 2500,
"items": [{"name": "服装", "category": "clothing"}]
}
}
# 输出: finalRate = 0.85 * 0.95 = 0.8075
response1 = await engine.async_evaluate("discount.json", input1)
print(response1)
# 测试用例2: 含数码类商品
input2 = {
"user": {"isVIP": True, "isBlacklisted": False},
"cart": {
"total": 2500,
"items": [{"name": "手机", "category": "digital"}]
}
}
# 输出: eligibleAmount=0 → finalRate=1.0
response2 = await engine.async_evaluate("discount.json", input2)
print(response2)
# 测试用例3: 黑名单用户
input3 = {
"user": {"isVIP": True, "isBlacklisted": True},
"cart": {
"total": 2500,
"items": [{"name": "服装", "category": "clothing"}]
}
}
# 输出: eligibleForDiscount=False → finalRate=1.0
response3 = await engine.async_evaluate("discount.json", input3)
print(response3)
asyncio.run(main())
在该demo中,规则文件在项目根目录的jdm_directory/discount.json中。JDM通过在线编辑器GoRules Editor进行编辑。然后下载Json文件即可。
下载下来的json文件为:
{
"contentType": "application/vnd.gorules.decision",
"nodes": [
{
"type": "inputNode",
"content": {
"schema": "{\n \"type\": \"object\",\n \"properties\": {\n \"user\": {\n \"type\": \"object\",\n \"properties\": {\n \"isVIP\": {\n \"type\": \"boolean\"\n },\n \"isBlacklisted\": {\n \"type\": \"boolean\"\n }\n }\n },\n \"cart\": {\n \"type\": \"object\",\n \"properties\": {\n \"total\": {\n \"type\": \"integer\"\n },\n \"items\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"object\",\n \"properties\": {\n \"name\": {\n \"type\": \"string\"\n },\n \"category\": {\n \"type\": \"string\"\n }\n }\n }\n }\n }\n }\n }\n}"
},
"id": "f78943f3-ae56-44cf-b553-b4c33930c90a",
"name": "Request",
"position": {
"x": 160,
"y": 245
}
},
{
"type": "decisionTableNode",
"content": {
"hitPolicy": "first",
"rules": [
{
"_id": "84ac149e-0ed9-468a-874b-8cdf4ade5431",
"_description": "",
"fa385065-e404-4145-af2a-8e65c9fec745": "true",
"b62dfc17-126b-42cd-b311-604e1abf9d27": "false"
},
{
"_id": "9e1144d3-e34f-481b-ac29-04b0a00292e5",
"_description": "",
"fa385065-e404-4145-af2a-8e65c9fec745": "false",
"b62dfc17-126b-42cd-b311-604e1abf9d27": "true"
}
],
"inputs": [
{
"id": "fa385065-e404-4145-af2a-8e65c9fec745",
"name": "UserIsBlacked",
"field": "user.isBlacklisted"
}
],
"outputs": [
{
"id": "b62dfc17-126b-42cd-b311-604e1abf9d27",
"name": "EligibleForDiscount",
"field": "eligibleForDiscount"
}
],
"passThrough": false,
"inputField": null,
"outputPath": null,
"executionMode": "single",
"passThorough": false
},
"id": "cd36e9a3-e711-4b4f-b310-a643a9495a76",
"name": "Black List",
"position": {
"x": 505,
"y": 245
}
},
{
"type": "decisionTableNode",
"content": {
"hitPolicy": "first",
"rules": [
{
"_id": "a4f6a175-ebc1-41c0-8e10-014b9850ca7c",
"3232fa61-73dc-4a12-802a-51ee0b01e8a0": "some(cart.items, #.category == \"digital\")",
"8dc679d2-52f2-4cd3-9d8c-e03ab9000111": "false"
},
{
"_id": "f358c02e-1261-498f-984b-44e04949fcf6",
"3232fa61-73dc-4a12-802a-51ee0b01e8a0": "not(some(cart.items, #.category == \"digital\"))",
"8dc679d2-52f2-4cd3-9d8c-e03ab9000111": "true"
}
],
"inputs": [
{
"id": "3232fa61-73dc-4a12-802a-51ee0b01e8a0",
"name": "Category",
"field": "cart.items"
}
],
"outputs": [
{
"id": "8dc679d2-52f2-4cd3-9d8c-e03ab9000111",
"name": "EligibleAmount",
"field": "eligibleAmount"
}
],
"passThrough": false,
"inputField": null,
"outputPath": null,
"executionMode": "single",
"passThorough": false
},
"id": "16efb624-32f0-4042-b283-329408b28e69",
"name": "Category Filter",
"position": {
"x": 510,
"y": 375
}
},
{
"type": "decisionTableNode",
"content": {
"hitPolicy": "first",
"rules": [
{
"_id": "ce872485-0bc5-442b-bae8-d9fdc632d7ed",
"03bd13c6-9717-42d9-9251-a89df9f59d20": "< 1000",
"6cbf8303-7c4a-4ef2-9f34-0a6fb4931e0c": "1"
},
{
"_id": "502c794c-844d-41d9-b0af-57d3dacc56e3",
"03bd13c6-9717-42d9-9251-a89df9f59d20": ">= 1000 and < 2000",
"6cbf8303-7c4a-4ef2-9f34-0a6fb4931e0c": "0.9"
},
{
"_id": "abef895e-b0a7-4a6f-9e7b-3518195307d8",
"03bd13c6-9717-42d9-9251-a89df9f59d20": ">= 2000",
"6cbf8303-7c4a-4ef2-9f34-0a6fb4931e0c": "0.85"
}
],
"inputs": [
{
"id": "03bd13c6-9717-42d9-9251-a89df9f59d20",
"name": "CartTotal",
"field": "cart.total"
}
],
"outputs": [
{
"id": "6cbf8303-7c4a-4ef2-9f34-0a6fb4931e0c",
"name": "BaseRate",
"field": "baseRate"
}
],
"passThrough": true,
"inputField": null,
"outputPath": null,
"executionMode": "single",
"passThorough": false
},
"id": "813891ff-8959-40cb-aeca-232024d6386c",
"name": "Base Rate",
"position": {
"x": 505,
"y": 505
}
},
{
"type": "decisionTableNode",
"content": {
"hitPolicy": "first",
"rules": [
{
"_id": "bcfe88fe-00d2-4854-95fc-bbce98859684",
"f7e3998b-4f19-4080-b660-d1abc6f0d457": "true",
"8f8e0fc3-5570-46f1-b108-5868194b149e": "",
"727d23e0-8bf2-499a-940a-ac3bad8e3e67": "baseRate * 0.95"
},
{
"_id": "e8538446-cdc2-4fed-8a0b-ad091121d44a",
"f7e3998b-4f19-4080-b660-d1abc6f0d457": "false",
"8f8e0fc3-5570-46f1-b108-5868194b149e": "",
"727d23e0-8bf2-499a-940a-ac3bad8e3e67": "baseRate"
}
],
"inputs": [
{
"id": "f7e3998b-4f19-4080-b660-d1abc6f0d457",
"name": "VIP",
"field": "user.isVIP"
},
{
"id": "8f8e0fc3-5570-46f1-b108-5868194b149e",
"name": "BaseRate",
"field": "baseRate"
}
],
"outputs": [
{
"id": "727d23e0-8bf2-499a-940a-ac3bad8e3e67",
"name": "VIPRate",
"field": "vipRate"
}
],
"passThrough": false,
"inputField": null,
"outputPath": null,
"executionMode": "single",
"passThorough": false
},
"id": "3a76eb8e-41f5-4c04-94ab-3c3460657222",
"name": "VIPRate",
"position": {
"x": 850,
"y": 510
}
},
{
"type": "expressionNode",
"content": {
"expressions": [
{
"id": "7c44b4db-5f1e-425c-9ce2-1b3fa89c493e",
"key": "finalRate",
"value": "not(eligibleForDiscount) ? 1 : (not(eligibleAmount) ? 1.0 : vipRate ) "
}
],
"passThrough": false,
"inputField": null,
"outputPath": null,
"executionMode": "single"
},
"id": "6bc22483-a157-4561-adfb-cbff2abc35c9",
"name": "Exception",
"position": {
"x": 1190,
"y": 355
}
},
{
"type": "outputNode",
"content": {
"schema": ""
},
"id": "b6c9f783-1713-4cec-b743-7e15272918ff",
"name": "Response",
"position": {
"x": 1605,
"y": 355
}
}
],
"edges": [
{
"id": "a98a6bd1-f9a2-44ba-86d8-6e08698876cf",
"sourceId": "f78943f3-ae56-44cf-b553-b4c33930c90a",
"type": "edge",
"targetId": "cd36e9a3-e711-4b4f-b310-a643a9495a76"
},
{
"id": "0d62f9d3-7686-4238-a54a-c409f20b85fe",
"sourceId": "f78943f3-ae56-44cf-b553-b4c33930c90a",
"type": "edge",
"targetId": "16efb624-32f0-4042-b283-329408b28e69"
},
{
"id": "5912f416-e323-4083-b6d5-3d5d3133e2cd",
"sourceId": "f78943f3-ae56-44cf-b553-b4c33930c90a",
"type": "edge",
"targetId": "813891ff-8959-40cb-aeca-232024d6386c"
},
{
"id": "c23550c9-fc1a-40cf-afc4-446f8e033292",
"sourceId": "813891ff-8959-40cb-aeca-232024d6386c",
"type": "edge",
"targetId": "3a76eb8e-41f5-4c04-94ab-3c3460657222"
},
{
"id": "4172df7f-d961-4b21-a8a8-9f5b42f4c817",
"sourceId": "cd36e9a3-e711-4b4f-b310-a643a9495a76",
"type": "edge",
"targetId": "6bc22483-a157-4561-adfb-cbff2abc35c9"
},
{
"id": "ca44d896-03d4-4bf9-abe6-0eed9ff58bc3",
"sourceId": "16efb624-32f0-4042-b283-329408b28e69",
"type": "edge",
"targetId": "6bc22483-a157-4561-adfb-cbff2abc35c9"
},
{
"id": "74d2e622-5d43-485d-b9f8-9d7e611747bb",
"sourceId": "3a76eb8e-41f5-4c04-94ab-3c3460657222",
"type": "edge",
"targetId": "6bc22483-a157-4561-adfb-cbff2abc35c9"
},
{
"id": "b7263fc5-a2b0-4322-bb4d-53073fe2e0ac",
"sourceId": "6bc22483-a157-4561-adfb-cbff2abc35c9",
"type": "edge",
"targetId": "b6c9f783-1713-4cec-b743-7e15272918ff"
}
]
}
具体使用可以参考官方手册Decision Management | GoRules Rules Engine
最后
zen-engine具有高性能,功能强大,采用json作为规则文件,易于理解和修改。是较好的一种方案。它可以无缝和Python,go和nodejs集成。Java需要通过JNI进行集成。
愿你我都能在各自的领域里不断成长,勇敢追求梦想,同时也保持对世界的好奇与善意!