microsoft / RulesEngine

A Json based Rules Engine with extensive Dynamic expression support
https://microsoft.github.io/RulesEngine/
MIT License
3.66k stars 549 forks source link

Access Rule(s) In Other Rules #221

Open sswapniljadhav opened 3 years ago

sswapniljadhav commented 3 years ago

Hello,

I am stuck with one scenario where I need to call multiple rules in 1 rule, here is simple example which I would like to achieve. Any help in solving the scenario or direction will be appreciated.

[ { "WorkflowName": "PaymentRule", "Rules": [ { "RuleName": "MinimumPayment", "SuccessEvent": "10", "ErrorMessage": "Error", "ErrorType": "Error", "RuleExpressionType": "LambdaExpression", "Operator": "OrElse", "Rules": [ { "RuleName": "IsPaymentEligible", "ErrorMessage": "Error", "ErrorType": "Error", "RuleExpressionType": "LambdaExpression", "Expression": "input1.PaymentAmount > 0 AND (input1.IsEligible==\"true\" AND input1.PaymentFrequency==input1.PaymentDuration ) AND input1.PaymentFrequency < input1.PaymentCycle ", "Actions": { "OnSuccess": { "Name": "OutputExpression", "Context": { "Expression": "(input1.PaymentAmount)" } } } }, { "RuleName": "elsePaymentEligible", "ErrorMessage": "Error", "ErrorType": "Error", "RuleExpressionType": "LambdaExpression", "Expression": "input1.PaymentAmount > 0 AND input1.IsEligible==\"false\" ", "Actions": { "OnSuccess": { "Name": "OutputExpression", "Context": { "Expression": "input1.PaymentFrequency" } } } } ]

  },
  {
    "RuleName": "BonusPayment",
    "SuccessEvent": "20",
    "ErrorMessage": "Error",
    "ErrorType": "Error",
    "RuleExpressionType": "LambdaExpression",
    "Operator": "OrElse",
    "Rules": [
      {
        "RuleName": "IsBonusEligible",
        "ErrorMessage": "Error",
        "ErrorType": "Error",
        "RuleExpressionType": "LambdaExpression",
        "Expression": "input1.BonusAmount > 0 AND (input1.IsBonusEligible==\"true\" AND input1.BonusFrequency==input1.BonusCycle)",
        "Actions": {
          "OnSuccess": {
            "Name": "OutputExpression",
            "Context": {
              "Expression": "(input1.BonusAmount)"
            }
          }
        }
      },
      {
        "RuleName": "elseBonusEligible",
        "ErrorMessage": "Error",
        "ErrorType": "Error",
        "RuleExpressionType": "LambdaExpression",
        "Expression": "input1.BonusAmount == 0 AND input1.IsBonusEligible==\"false\" ",
        "Actions": {
          "OnSuccess": {
            "Name": "OutputExpression",
            "Context": {
              "Expression": "input1.FlatBonus"
            }
          }
        }
      }
    ]

  },
  {
    "RuleName": "PaymentAmountToClient",
    "SuccessEvent": "30",
    "ErrorMessage": "Error",
    "ErrorType": "Error",
    "RuleExpressionType": "LambdaExpression",
    "Expression": "input1.PaymentDueDate == input1.PaymentDate",
    "Actions": {
      "OnSuccess": {
        "Name": "OutputExpression", 
        "Context": { 
          "Expression": "input1.BalanceAmount + {BonusPayment} + {MinimumPayment}"
        }
      }
    }

  }
]

} ]

asulwer commented 3 years ago

copy/paste and then nest? maybe not elegant but its a possible solution? although it would be nice to reference other rules like include a header file (c++) or use using (c#)

abbasc52 commented 3 years ago

@sswapniljadhav Are you looking for referencing Rules into another rule or are you looking at referencing output of Rules within another rule?

sswapniljadhav commented 3 years ago

@sswapniljadhav Are you looking for referencing Rules into another rule or are you looking at referencing output of Rules within another rule?

Referencing output of rules in another rule

alexreich commented 3 years ago

@sswapniljadhav It sounds you should use ScopedParams (see also LocalParams). For across rules and accessing the expression via other rules (really just expressions) use a GlobalParam (inside the Workflow object) with the original rule as the expression and then reference the global param in the other rules. In the original rule, the expression would simply be the name of the global param and in the others it would be GlobalParam1 && Input1.Foo == 0. More info, here are the unit tests, take a look at the sample workflows at the bottom here.

widavies commented 2 years ago

I have a fork that does this if anyone is interested. I'm not really sure if it's pull request material though.

alexreich commented 2 years ago

I have a fork that does this if anyone is interested. I'm not really sure if its pull request material though.

Is this what you mean? Can you put it in a new branch and create some unit tests for it?

widavies commented 2 years ago

That's one small portion of it. I'm working on a project that required me to fork RulesEngine to add a few features I needed that weren't already baked in. As a disclaimer, I'm focused on moving my project forward so my implementations are "quick and dirty" so to speak, while they are functional, they aren't necessarily ideal.

With that said, my fork (in its current state) adds the following features:

The way I implement this is maybe a bit hacky, the DAG is traversed and rules are registered and executed one at a time. When a rule is complete, I package its result into a GlobalParam which is provided to the execution context of all subsequent rules. This GlobalParam method is a bit of a kludge, but it allowed me to implement the feature with minimal effort and changes to the code. I'm not sure if it's the best way as well. Secondly, I share a Value, not the Expression between rules, this is because my project has tags that are passed along instead of expressions so I'm not sure if this is something other people would want, or if we should just switch this to Expression references instead.

Here is an example of how my rule referencing system works:

[
  {
    "WorkflowName": "Workflow1",
    "Rules": [
      {
        "RuleName": "HasDiscount",
        "Expression": "FetchDiscount > 0"
      },
      {
        "RuleName": "FetchDiscount",
        "Operator": "ExclusiveOr",
        "DefaultValue": "0",
        "Rules": [
          {
            "RuleName": "Discount20",
            "Value": "20",
            "ExpressionType": "LambdaExpression",
            "Expression": "input1.loyaltyFactor <= 2"
          },
          {
            "RuleName": "Discount30",
            "Value": "30",
            "ExpressionType": "LambdaExpression",
            "Expression": "input1.loyaltyFactor >= 3"
          }
        ]
      }
    ]
  }
]

First, my altered RuleEngine noticies that the rule HasDiscount depends on FetchDiscount, so it will set up an execution plan where FetchDiscount is executed first, and HasDiscount is executed second. Secondly, the ExclusiveOr requires exactly one of its children to evaluate to true (IsSuccess = true). If this is not the case, the rule will fail — with one exception: if all child rules fail DefaultValue is returned. Value doesn't need to be a fixed value like it is here, it could be an Expression as well. I haven't added support for Values referencing other rules or values yet. Finally the Value evaluated from FetchDiscount is inserted into the expression HasDiscount and HasDiscount is registered and executed.

Let me know if you have any questions.