规则引擎.Net Core

.Net Core 环境下构建强大且易用的规则引擎

https://www.cnblogs.com/chenug/p/9160397.html

本文源码: https://github.com/jonechenug/ZHS.Nrules.Sample

  1. 引言
    1.1 为什么需要规则引擎
    在业务的早期时代,也许使用硬编码或者逻辑判断就可以满足要求。但随着业务的发展,越来越多的问题会暴露出来:

逻辑复杂度带来的编码挑战,需求变更时改变逻辑可能会引起灾难
重复性的需求必须可重用,否则必须重复性编码
运行期间无法即时修改规则,但重新部署可能会带来其他问题
上线前的测试变得繁琐且不可控,必须花大量的人力和时间去测试
这些困境在『 小明历险记:规则引擎 drools 教程一』 一文中可以体会一番,一开始只是简单的根据购物金额来发放积分,运行期间又要更改为更多的规则层次,如果不及时引入对应的规范化处理机制,开发人员将慢慢坠入无止尽的业务深渊。对此,聪明的做法是在系统中引入规则引擎,对业务操作员要提供尽量简单的操作页面来配置规则,规则引擎和配置尽量不要耦合到一块。

1.2 .Net Core 环境下的选择 -- Nrules
目前最流行的规则引擎应该是Drools, 用 Java 语言编写的开放源码规则引擎,使用 Rete 算法对所编写的规则求值,其操作流程如下:

Drools 操作流程

对于 .Net 应用来说,可以通过 Kie 组件提供的 Rest 接口调用规则引擎运算。然而其过于庞大,仅仅只是需要规则引擎计算核心的部分。对此,查找了 .Net 中开源的规则引擎,发现只有同样实现 Rete 算法的 Nrules 满足要求(支持 .Net Core,运行时加载规则引擎)。

注:本文参考借鉴了美团技术团队 从 0 到 1:构建强大且易用的规则引擎 一文的设计思路,对 Drools 从入门到放弃。

  1. Nrules 实战 -- 电商促销活动规则引擎设计
    2.1 了解 Nrules
    NRules 是基于 Rete 匹配算法的.NET 生产规则引擎,基于.NET Standard ,支持 4.5+ 的应用,提供 流式声明规则、运行时构建规则、专门的规则语言(开发中,不推荐使用到生产,基于.Net 4.5 而不是 .NETStandard )。
    其计算机制也与其他规则引擎大同小异:
    计算机制

2.2 设计规则配置
前文提到 对业务操作员要提供尽量简单的操作页面来配置规则 ,所以我们定义促销活动的规则配置就要尽量简单。

业务操作员眼中的规则

在设计模型时,我们必须先参考现实生活中遇到的电商促销活动,大致可以想到有这么几种活动类型:满减促销、单品促销、套装促销、赠品促销、满赠促销、多买优惠促销、定金促销等。
在这里,我选择对多买优惠促销做分析,多买促销优惠即所谓的阶梯打折,如买一件9折,买两件8折,其模型大致如下:

public class LadderDiscountPromotion
{
    public List<LadderDiscountRuleItem> Rules { get; set; }
    public string Name { get; set; }
    public DateTime StarTime { get; set; }
    public DateTime EndTime { get; set; }
    public PromotionState State { get; set; }
    public List<string> ProductIdRanges { get; set; }
    public bool IsSingle { get; set; }
    public string Id { get; set; }
}

public class LadderDiscountRuleItem
{
    /// <summary>
    /// 数量
    /// </summary>
    public Int32 Quantity { get; set; }

    /// <summary>
    /// 打折的百分比
    /// </summary>
    public Decimal DiscountOff { get; set; }
}

这里为了简化设计,设计的模型并不会去约束平台、活动范围、会员等级等,仅仅约束了使用的产品 id 范围。为了匹配现实中可能出现的组合优惠(类似满减活动后还可以使用优惠券等)现象和相反的独斥现象(如该商品参与xx活动后不支持X券),设置了一个字段来判断是否可以组合优惠,也可以理解为所有活动都为组合优惠,只是有些组合优惠只有一个促销活动。

注:想了解更多关于电商促销系统设计可参考脑图

2.3 规则配置转换
为了实现 规则引擎和配置尽量不要耦合到一块,必须有中间层对规则配置进行转换为 Nrules 能够接受的规则描述。联系前文的计算机制,我们可以得到这样一个描述模型:

public class RuleDefinition
{
    /// <summary>
    /// 规则的名称
    /// </summary>
    public String Name { get; set; }
    /// <summary>
    /// 约束条件
    /// </summary>
    public List<LambdaExpression> Conditions { get; set; }
    /// <summary>
    ///  执行行动
    /// </summary>
    public  List<LambdaExpression> Actions { get; set; }
}

由于 Nrules 支持流式声明,所以约束条件和产生的结果都可以用 LambdaExpression 表达式实现。现在我们需要把阶梯打折的配置转换成规则描述,那我们需要先分析一下。假设满一件9折,满两件8折,满三件7折,那我们可以将其分解为:

大于等于三件打 7 折
大于等于两件且小于三件打 8 折
大于等于一件且小于两件 9 折
基于此分析,我们可以看出,只有第一个最多的数量规则是不一样的,其他规则都是比前一个规则的数量小且大于等于当前规则的数量,那么我们可以这样转换我们的规则配置:

List BuildLadderDiscountDefinition(LadderDiscountPromotion promotion)
{
var ruleDefinitions = new List();
//按影响的数量倒叙
var ruleLimits = promotion.Rules.OrderByDescending(r => r.Quantity).ToList();
var currentIndex = 0;
var previousLimit = ruleLimits.FirstOrDefault();
foreach (var current in ruleLimits)
{
//约束表达式
var conditions = new List();
var actions = new List();
if (currentIndex == 0)
{
Expression> conditionPart =
o => o.GetRangesTotalCount(promotion.ProductIdRanges) >= current.Quantity;
conditions.Add(conditionPart);
}
else
{
var limit = previousLimit;
Expression> conditionPart = o =>
o.GetRangesTotalCount(promotion.ProductIdRanges) >= current.Quantity
&& o.GetRangesTotalCount(promotion.ProductIdRanges) < limit.Quantity;
conditions.Add(conditionPart);
}
currentIndex = currentIndex + 1;

            //触发的行为表达式
            Expression<Action<Order>> actionPart =
                o => o.DiscountOrderItems(promotion.ProductIdRanges, current.DiscountOff, promotion.Name, promotion.Id);
            actions.Add(actionPart);

            // 增加描述
            ruleDefinitions.Add(new RuleDefinition
            {
                Actions = actions,
                Conditions = conditions,
                Name = promotion.Name
            });
            previousLimit = current;
        }
        return ruleDefinitions;
    }

2.4 生成规则集合
在 Nrules 的 wiki 中,为了实现运行时加载规则引擎,我们需要引入实现 IRuleRepository ,所以我们需要将描述模型转换成 Nrules 中的 RuleSet:

public class ExecuterRepository : IRuleRepository, IExecuterRepository
{
    private readonly IRuleSet _ruleSet;
    public ExecuterRepository()
    {
        _ruleSet = new RuleSet("default");
    }

    public IEnumerable<IRuleSet> GetRuleSets()
    {
        //合并
        var sets = new List<IRuleSet>();
        sets.Add(_ruleSet);
        return sets;
    }

    public void AddRule(RuleDefinition definition)
    {
        var builder = new RuleBuilder();
        builder.Name(definition.Name);
        foreach (var condition in definition.Conditions)
        {
            ParsePattern(builder, condition);
        }
        foreach (var action in definition.Actions)
        {
            var param = action.Parameters.FirstOrDefault();
            var obj = GetObject(param.Type);
            builder.RightHandSide().Action(ParseAction(obj, action, param.Name));
        }
        _ruleSet.Add(new[] { builder.Build() });
    }

    PatternBuilder ParsePattern(RuleBuilder builder, LambdaExpression condition)
    {
        var parameter = condition.Parameters.FirstOrDefault();
        var type = parameter.Type;
        var customerPattern = builder.LeftHandSide().Pattern(type, parameter.Name);
        customerPattern.Condition(condition);
        return customerPattern;
    }

    LambdaExpression ParseAction<TEntity>(TEntity entity, LambdaExpression action, String param) where TEntity : class, new()
    {
        return NRulesHelper.AddContext(action as Expression<Action<TEntity>>);
    }

}

2.5 执行规则引擎
做了转换处理仅仅是第一步,我们还必须创建一个规则引擎的处理会话,并把相关的事实对象(fact)传递到会话,执行触发的代码,相关对象发生了变化,其简单代码如下:

var repository = new ExecuterRepository();
//加载规则
repository.AddRule(new RuleDefinition());
repository.LoadRules();
// 生成规则
ISessionFactory factory = repository.Compile();
// 创建会话
ISession session = factory.CreateSession();
// 加载事实对象
session.Insert(new Order());
// 执行
session.Fire();
2.6 应用场景示例
我们假设有这么一个应用入口:传入一个购物车(这里等价于订单)id,获取其可以参加的促销活动,返回对应活动优惠后的结果,并按总价的最低依次升序,那么可以这么写:

   public IEnumerable<AllPromotionForOrderOutput> AllPromotionForOrder([FromQuery]String id)
    {
        var result = new List<AllPromotionForOrderOutput>();
        var order = _orderService.Get(id) ?? throw new ArgumentNullException("_orderService.Get(id)");
        var promotionGroup = _promotionService.GetActiveGroup();
        var orderjson = JsonConvert.SerializeObject(order);
        foreach (var promotions in promotionGroup)
        {
            var tempOrder = JsonConvert.DeserializeObject<Order>(orderjson);
            var ruleEngineService = HttpContext.RequestServices.GetService(typeof(RuleEngineService)) as RuleEngineService;
            ruleEngineService.AddAssembly(typeof(OrderRemarkRule).Assembly);
            ruleEngineService.ExecutePromotion(promotions, new List<object>
            {
                tempOrder
            });
            result.Add(new AllPromotionForOrderOutput(tempOrder));
        }
        return result.OrderBy(i => i.Order.GetTotalPrice());
    }

假设这么一个购物车id,买一件时最优惠是参加 A 活动,买两件时最优惠是参加 B 和 C 活动,那么其效果图可能如下:

不同的条件对规则的影响

  1. 结语
    本文只是对规则引擎及 Nrules 的简单介绍及应用,过程中隐藏了很多细节。在体会到规则引擎的强大的同时,还必须指出其局限性,规则引擎同样不是银弹,必须结合实际出发。

扩展阅读:Martin Fowler:应该使用规则引擎吗?

原文地址:https://www.cnblogs.com/Leo_wl/p/9170336.html

时间: 2024-10-09 23:58:20

规则引擎.Net Core的相关文章

.Net Core 环境下构建强大且易用的规则引擎

本文源码: https://github.com/jonechenug/ZHS.Nrules.Sample 1. 引言 1.1 为什么需要规则引擎 在业务的早期时代,也许使用硬编码或者逻辑判断就可以满足要求.但随着业务的发展,越来越多的问题会暴露出来: 逻辑复杂度带来的编码挑战,需求变更时改变逻辑可能会引起灾难 重复性的需求必须可重用,否则必须重复性编码 运行期间无法即时修改规则,但重新部署可能会带来其他问题 上线前的测试变得繁琐且不可控,必须花大量的人力和时间去测试 这些困境在『 小明历险记:

jboss规则引擎KIE Drools 6.3.0 Final 教程(2)

使用JAVA程序调用规则-运行KIE-DROOLS上的规则 第一步:建立一个MAVEN的Java工程 POM.XML 给出pom.xml文件 <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0

业务规则引擎浅析

在CRM(客户关系管理)系统或者其他业务支撑型系统的开发过程中,最经常多变的就是复杂的业务规则.因为这些规则要迎合.顺应市场的变化,如何能有效到做到业务规则和整体的系统支撑架构解耦分离,这个是开发过程中必须考虑的一个问题.每当客户要求改变一个业务规则的时候,我们又如何能做到在最短的时间内完成需求的开发提交,提高系统的灵活度?业务规则引擎无非是一个比较好的解决方案.它把复杂.冗余的业务规则同整个支撑系统分离开,做到架构的可复用移植,这个就是我们的终极目标. 那规则引擎又是什么东西?严格来说,它是一

开源工作流引擎 Workflow Core 的研究和使用教程

目录 开源工作流引擎 Workflow Core 的研究和使用教程 一,工作流对象和使用前说明 二,IStepBuilder 节点 三,工作流节点的逻辑和操作 容器操作 普通节点 事件 条件体和循环体 节点的异步或多线程 用于事务的操作 四,条件或开关 迭代 条件判断 节点并发 五,其它 开源工作流引擎 Workflow Core 的研究和使用教程 一,工作流对象和使用前说明 为了避免歧义,事先约定. 工作流有很多节点组成,一个节点成为步骤点(Step). 1,IWorkflow / IWork

规则引擎入门

什么是规则引擎? 规则引擎由推理引擎发展而来,是一种嵌入在应用程序中的组件,实现了将业务决策从应用程序代码中分离出来,并使用预定义的语义模块编写业务决策.接受数据输入,解释业务规则,并根据业务规则做出业务决策. 参考: http://baike.baidu.com/link?url=BfAR-sxz1ryvOZ4uSVCqn9Csy51U5qCUaIomAQBogPE7FOxbbDB-2iXWR6N8FReIkoNNdc-Hk4y0sxKWpknWd_ 规则引擎和工作流引擎有什么区别? 工作流引

.Net规则引擎介绍 - REngine

规则引擎 规则引擎由推理引擎发展而来,是一种嵌入在应用程序中的组件,实现了将业务决策从应用程序代码中分离出来,并使用预定义的语义模块编写业务决策.接受数据输入,解释业务规则,并根据业务规则做出业务决策. 个人理解 变化点过于集中在一点,为了适应变化,而给各种场景分别写了各自关于那点的代码逻辑,尽管主要的业务逻辑相同. 适用点举例 计算折扣,需要根据客户的积分决定打折点 工作流具体节点的路由控制 其他(希望大家补充下,想象力有限...) 用法 以计算客户打折点为例: 我们首先得新建一个规则文件,用

规则引擎

规则引擎由推理引擎发展而来,是一种嵌入在应用程序中的组件,实现了将业务决策从应用程序代码中分离出来,并使用预定义的语义模块编写业务决策.接受数据输入,解释业务规则,并根据业务规则做出业务决策. 应用背景: 企业级管理者对企业IT系统的开发有着如下的要求: 1.为提高效率,管理流程必须自动化,即使现代商业规则异常复杂. 2.市场要求业务规则经常变化,IT系统必须依据业务规则的变化快速.低成本的更新. 3.为了快速.低成本的更新,业务人员应能直接管理IT系统中的规则,不需要程序开发人员参与. 使用规

规则引擎设计概况

在一个系统中业务规则占据了相当大的比例,而且是变动最为频繁的,我们总是希望把容易变动的和不容易变动的分离开来,这样就不至于因为修改易变的部分影响不需变的部分,从而简化系统修改的复杂性,也提高系统的灵活性. 在一个系统中我们把组成部分拆分为数据,逻辑,界面等几个部分,其中数据又可以进一步拆分为水平和垂直部分,对于逻辑绝大部分是”如果-那么”这种形式,对于界面部分可拆分为页面,控件(文本框,下拉框,日期,文件,图片,单选框,多选框等)和展示权限(可看,可编辑). 业务逻辑从本质上来讲就是一种规则的集

使用规则引擎Drools计算圆周率PI

实际上是使用规则引擎能够更新工作内存区重新匹配规则实现迭代功能. 使用了策略模式实现. <规则引擎与RETE算法介绍> PPT : http://files.cnblogs.com/lovesqcc/%E8%A7%84%E5%88%99%E5%BC%95%E6%93%8E%E4%B8%8ERETE%E7%AE%97%E6%B3%95.pptx 1.  CalcPI.java package sample; import java.util.ArrayList; import java.util