This article source: https://github.com/jonechenug/ZHS.Nrules.Sample
1. Introduction 1.1 Why a rule engine is required
In the early days of business, it might be possible to use hard-coded or logical judgments to meet the requirements. But with the development of business, more and more problems will be exposed:
- Logic complexity brings coding challenges, changing logic when requirements change can cause disaster
- Repetitive requirements must be reusable, otherwise repetitive coding is required
- Rules cannot be modified on-the-fly during runtime, but re-deployment may cause additional problems
- Pre-launch testing becomes cumbersome and uncontrolled and requires a lot of manpower and time to test
These predicament in "Xiao Ming Adventures: Rule Engine drools Tutorial One" article can be realized, at first just simple according to the purchase amount to distribute points, the operation period also changed to more rules, if not in time to introduce the corresponding normalization processing mechanism, developers will slowly fall into the endless business abyss. The smart way to do this is to introduce a rule engine into the system, to configure rules for business operators to provide as simple an action page as possible, and to avoid coupling the rules engine and configuration to one piece.
1.2. Net Core Environment Selection--nrules
The most popular rule engine today should be drools, an open source rule engine written in the Java language, using the RETE algorithm to evaluate the rules that are written, with the following operating procedures:
For. Net applications, the rule engine operation can be invoked through the Rest interface provided by the Kie component. However, it is too large to be just a part of the core of the rule engine calculation. In this case, the open source rule engine in. NET was found, and only nrules satisfying the requirements (support for. NET Core, runtime load Rule engine) were discovered only if the Rete algorithm was also implemented.
Note: This article references the American team from 0 to 1: Build a powerful and easy-to-use rule engine in the design of the article, the Drools from the introduction to abandon.
2. Nrules actual Combat--e-commerce promotion rules engine Design 2.1 understanding Nrules
Nrules is a. NET production rules engine based on the Rete matching algorithm, based on. NET Standard, which supports 4.5+ applications, provides streaming claim rules, runtime build rules, specialized rule languages (in development, not recommended for production, based on. NET 4.5 instead. Netstandard).
Its computational mechanism is similar to other rule engines:
2.2 Design Rule Configuration
The previous article mentioned the configuration of rules for business operators to provide the simplest possible action page , so we define the rules for promotion activities as simple as possible.
In the design of the model, we must first refer to the real life encountered in the e-commerce promotion, can think of a few types of activities: full-cut promotions, single-item promotions, package promotions, gift promotions, full-gift promotions, multi-buy preferential promotions, deposit promotion.
Here, I choose to buy more preferential promotions do analysis, buy more promotional offers that is called the ladder discount, such as buy a 90 percent, buy two pieces 80 percent, the model is roughly as follows:
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; } }
In order to simplify the design, the model does not constrain the platform, the scope of activities, the membership level, etc., and only constrains the range of product IDs used. In order to match the reality may appear in the combination of offers (like the full reduction activities can also use coupons, etc.) phenomenon and the opposite of the exclusive phenomenon (such as the product does not support x-coupons after XX activities), set up a field to determine whether the offer can be combined, it can be understood that all activities are combined offers, Just some combination offers only one promotion.
Note: To learn more about e-commerce promotion system design can refer to the brain map
2.3 Rule Configuration Transformation
In order to implement the rule engine and configuration as far as possible without coupling to a piece , there must be a middle tier to convert the rule configuration to nrules acceptable rule description. By contacting the computer system of the previous article, we can get a description of this model:
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; } }
Because Nrules supports streaming declarations, both constraints and resulting results can be implemented with lambdaexpression expressions. Now we need to convert the ladder discount configuration into a rule description, so we need to analyze it first. Assuming a full 90 percent, full two pieces 80 percent, full three pieces 70 percent, then we can break it down into:
- Greater than or equal to three pieces 70 percent
- Greater than or equal to two pieces and less than three pieces 80 percent
- Greater than or equal to one piece and less than two pieces 90 percent
Based on this analysis, we can see that only the first maximum number of rules is different, and the other rules are smaller than the number of previous rules and equal to the number of current rules, then we can convert our rule configuration like this:
List<ruledefinition> buildladderdiscountdefinition (ladderdiscountpromotion promotion) {var ruleDe Finitions = new list<ruledefinition> (); By the number of effects flashback var rulelimits = promotion. Rules.orderbydescending (r = r.quantity). ToList (); var currentindex = 0; var previouslimit = Rulelimits.firstordefault (); foreach (Var-in rulelimits) {//constraint expression var conditions = new List<lambda Expression> (); var actions = new list<lambdaexpression> (); if (Currentindex = = 0) {expression<func<order, bool>> Conditionpart = o = O.getrangestotalcount (promotion. productidranges) >= Current. Quantity; Conditions. ADD (Conditionpart); } else {var limit = Previouslimit; ExprEssion<func<order, bool>> conditionpart = O = O.getrangestotalcount (promotion. productidranges) >= Current. Quantity && o.getrangestotalcount (promotion. Productidranges) < limit. Quantity; Conditions. ADD (Conditionpart); } currentindex = Currentindex + 1; Triggered behavior expression expression<action<order>> Actionpart = O = O.discountorderite MS (promotion. Productidranges, current. Discountoff, promotion. Name, promotion. ID); Actions. ADD (Actionpart); Add Description Ruledefinitions.add (new ruledefinition {actions = actions, Conditions = Conditions, Name = promotion. Name}); Previouslimit = current; } return ruledefinitions; }
2.4 Generating Rule collections
In Nrules's wiki, in order to implement the runtime load rule engine, we need to introduce implementation irulerepository, so we need to convert the description model to RuleSet in Nrules:
public class Executerrepository:irulerepository, iexecuterrepository {private readonly iruleset _ruleset; Public Executerrepository () {_ruleset = new RuleSet ("Default"); } public ienumerable<iruleset> Getrulesets () {//merge var sets = new List<irule Set> (); 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 Execution Rule Engine
The conversion process is only the first step, we must also create a rule engine processing session, and the relevant Facts object (fact) passed to the session, executing the triggered code, the related objects have changed, its simple code is as follows:
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 Scenario Examples
Let's say that there is an application Portal: Pass in a shopping cart (here equivalent to the order) ID, get the promotion that it can participate in, return the result after the promotion of the event, and in ascending order by the lowest price, then you can write:
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<or Der> (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. (i = I.order.gettotalprice ()); }
Suppose such a shopping cart ID, buy one when the most preferential is to participate in A activity, buy two pieces when the most preferential is to participate in B and C activities, then it may be as follows:
3. Conclusion
This article simply introduces and applies the rules engine and nrules, and hides a lot of details in the process. In the sense of the power of the rule engine, it must also point out its limitations, the rule engine is also not a silver bullet, must be combined with the actual departure.
Extended reading: Martin Fowler: Should I use the rule engine?
Build a powerful and easy-to-use rule engine in a. Net Core environment