Tuesday, June 28, 2011

Build XML based Business Rule Engine in .NET 4 – Post 3 (write runtime condition predicate)

It is continuation of first post - << click here to read
and second post - < click here to read

Dynamicity is of great advantage. First of all let us move forward step wise, define delegate to a separate method;
rule.AddCondtion(po => po.NetProductPrice >= 0);
should tends to
rule.AddCondtion(IsPriceGreaterOrEqualToZero());

public static Predicate<PriceObject> IsPriceGreaterOrEqualToZero()
{
    return delegate(PriceObject po) { return po.NetProductPrice >= 0; };
}

Let us consider even more generic function to write. Point is every condition returns  Predicate<PriceObject> and internally the condition result is a Boolean variable (true/false). Why not create runtime generic delegate to handle Equals operator for example. We will make it compatible to other operators later.

public static Predicate<T> GetConditiPredicate<T>(string fieldName, string operatorName, string value)
{
    Type itemType = typeof(T);
    ParameterExpression predParam = Expression.Parameter(itemType, "po");
    PropertyInfo propertyInfo = itemType.GetProperty(fieldName);
    Expression left = Expression.Property(predParam, propertyInfo);
    Expression right = Expression.Constant(ObjTypeConverter.ChangeType(value, propertyInfo.PropertyType), propertyInfo.PropertyType);
    Expression equality = Expression.GreaterThanOrEqual(left, right);
    Func<T, bool> function = (Func<T, bool>)Expression.Lambda(equality, new[] { predParam }).Compile();
    return new Predicate<T>(function);
}

And caller can deliver a call like this

rule.AddCondtion(Dynamic.GetConditiPredicate<PriceObject>("DRM", "Equals", "2"));

Well compile and see if it works fine. It should :)

Now let us replace the statement with more general one….
//Expression equality = Expression.GreaterThanOrEqual(left, right);
Expression result = GetExpression(operatorName, left, right);
Func<T, bool> function = (Func<T, bool>)Expression.Lambda(result, new[] { predParam }).Compile();

And

public static Expression GetExpression(string operatorName, Expression left, Expression right)
{
    Expression result = null;
    switch (operatorName.ToLower())
    {
        case "equals":
            result = Expression.Equal(left, right);
            break;
        case "greaterorequal":
            result = Expression.GreaterThanOrEqual(left, right);
            break;
        case "lessorequal":
            result = Expression.LessThanOrEqual(left, right);
            break;
        case "greaterthan":
            result = Expression.GreaterThan(left, right);
            break;
        case "lessthan":
            result = Expression.LessThan(left, right);
            break;
        case "add":
            result = Expression.Add(left, right);
            break;
        case "subtract":
            result = Expression.Subtract(left, right);
            break;
        case "multiply":
            result = Expression.Multiply(left, right);
            break;
        case "divide":
            result = Expression.Divide(left, right);
            break;
        default:
            throw new Exception("Unknown Operator Name");
    }
    return result;
}
           
 This was simple to handle isolated conditions. How to handle Actions that may depend one another give us new dimension.

If want to play with Rule Engine source code - I extracted from my business case for demo purpose. download from skydrive. (FileName: ConfigurableRuleEngine.zip)
Comments please   

Saturday, June 25, 2011

Build XML based Business Rule Engine in .NET 4 – Post 2 (Design a Model)

It is continuation of first post - << click here to read

Let us start thinking about Rule Engine model.  
Entity 1: Price Object
To keep it simple I should define a Price Object, on which whole calculation will be performed to evaluate Transaction Fee.
public class PriceObject
{
    public decimal NetProductPrice { get; set; }
    public int DRM { get; set; }
    public decimal TransactionFee  { get; set; }
}
This object could be any thing in your scenario, we very much like it feed to the Rule Engine. I would rather say it 'Communication Channel or DTO'. Rule Engine apply defined rules on Channel Entity and provide results (in my case calculate and fill up TransactionFee).  
In some cases, it makes sense to have one input channel entity and rule engine return another channel entity.

Entity 2: Rule Engine
To keep rule engine simple, at high level, it contains a list of rules defined in XML per distributor. When caller request to run the rule engine, it supplies distributor identification and price object in question. Rule engine just pick the rules defined in xml in top-down order and run each one of them.
public class RuleEngine
{
    IDictionary<int, IRule> Rules = new Dictionary<int, IRule>();

    public void AddRule(IRule rule)
    {
        Rules.Add(Rules.Count(), rule);
    }

    public void Run(string distributorKey, PriceObject obj)
    {
        var rules = Rules.Where(dr => dr.Value.DistributorName == distributorKey)
.OrderBy(kv=>kv.Key)
.Select(kv=>kv.Value)
.ToList();
        foreach (var r in rules)
        {
            r.Run(obj);
        }
    }

}

Entity 3: Rule Definition
Each rule is based on interface IRule that defines:
  1. -          IRule must know Distributor Name / identification whom it belongs to
  2. -          Can add conditions as runtime delegate (Predicate) and logical operation to inference using forward chaining mechanism
  3. -          Can add actions taken on Price Object type at runtime delegate (Custom one defined by me)
  4. -          Run the rule on actual Price Object
public interface IRule
{
    string DistributorName { get; }
    void AddCondtion(Predicate<PriceObject> condition, LogicalOperation operation = LogicalOperation.AND);
    void AddAction(ActionPredicate<PriceObject> action);
    void Run(PriceObject obj);
}

Then comes IRule implementation named ConcreteRule. It encapsulates a list of Conditions and List of Actions. When running a rule, it uses forward chaining and infers the final result (Boolean true or false) after evaluating each condition. Once get a conditions result it apply simple rule: 


IF conditions_result is true THEN execute each action sequentially END.

Here is simple code:
public class ConcreteRule : IRule
{
    IList<ICondition> conditions = new List<ICondition>();
    IList<IAction> actions = new List<IAction>();

    public string DistributorName { get; private set; }

    public ConcreteRule(string distributorName)
    {
        DistributorName = distributorName;
    }
    public void AddCondtion(Predicate<PriceObject> condition, LogicalOperation operation = LogicalOperation.AND)
    {
        conditions.Add(new RuleCondition(condition, operation));
    }
    public void AddAction(ActionPredicate<PriceObject> action)
    {
        actions.Add(new RuleAction(action));
    }

    public void Run(PriceObject obj)
    {
        bool result = true;
        foreach (var c in conditions)
        {
            var res = c.Validate(obj);

            if (c.Operation == LogicalOperation.AND)
            {
                result = result && res;
            }
            else
            {
                result = result || res;               
            }
        }

        if (result)
        {
            foreach (var a in actions)
            {
  a.Execute(obj);
            }
        }
    }

}

Entity 3: Condition Definition
Each condition is based on interface ICondition that defines:
  1. -          ICondition must know Logical Operator to infer final flag (forward chaining)
  2. -          ICondition can operate on actual Price Object for validation
public interface ICondition
{
    LogicalOperation Operation { get; }
    bool Validate(PriceObject obj);
}

And here is concrete Condition Implementation class named RuleCondition. You should focus on Validate method how simple it executes the delegate?
public class RuleCondition : ICondition
{
    readonly Predicate<PriceObject> Conditions;
      
    public LogicalOperation Operation
    {
        get;
        private set;
    }

    public RuleCondition(Predicate<PriceObject> conditions, LogicalOperation operation)
    {
        this.Conditions = conditions;
        this.Operation = operation;
    }

    public bool Validate(PriceObject priceObject)
    {
        return Conditions(priceObject);
    }
}

Entity 4: Action Definition
Each action is based on interface IAction with simple concept:
  1. -          IAction can execute a command on Price Object at runtime.
 Just I need a custom delegate named Action Predicate to return decimal. I cannot go more generic as I always deal with Transaction Fee calculation so limit it to return decimal value. Note T parameter in constructed type is contravariant.     
public delegate decimal ActionPredicate<in T>(T obj);

public interface IAction
{
    decimal Execute(PriceObject obj);
}

And here is concrete Action Implementation class named RuleAction. You should remember executes method and the delegate usage.
public class RuleAction : IAction
{
    readonly ActionPredicate<PriceObject> Action;

    public RuleAction(ActionPredicate<PriceObject> action, string key = null)
    {
        this.Action = action;
        this.Key = key;

    }

    public string Key
    {
        get;
        private set;
    }

    public decimal Execute(PriceObject priceObject)
    {
        return Action(priceObject);          
    }
}

Caller Module: Let us first write a static module and define hard coded rules for execution. J
private void DoWork()
{
    var engine = new RuleEngine();
    IRule rule = new ConcreteRule("AMAZON");
    rule.AddCondtion(po => po.NetProductPrice >= 0);
    rule.AddAction(po => po.TransactionFee = Math.Round(po.NetProductPrice * 0.05M, 2));
    engine.AddRule(rule);

    rule = new ConcreteRule("AMAZON");
    rule.AddCondtion(po => po.DRM == 2);
    rule.AddCondtion(po => po.TransactionFee < 4M);
    rule.AddAction(po => po.TransactionFee = 4M);
    engine.AddRule(rule);

    rule = new ConcreteRule("AMAZON");
    rule.AddCondtion(po => po.DRM == 3);
    rule.AddCondtion(po => po.TransactionFee < 8M);
    rule.AddAction(po => po.TransactionFee = 8M);
    engine.AddRule(rule);

    rule = new ConcreteRule("AMAZON");
    rule.AddCondtion(po => po.TransactionFee > 10M);
    rule.AddAction(po => po.TransactionFee = 10M);
    engine.AddRule(rule);

    var obj = new PriceObject
    {
        NetProductPrice = 100,
        DRM = 3
    };

    engine.Run("AMAZON", obj);

    MessageBox.Show(obj.TransactionFee.ToString());
}

You can execute and ensure how beautifully rule engine is providing correct results.
Next is to think about possibility how to define runtime delegate so that I can wire up XML inputs to define dynamic conditions and actions.
My short target is to replace statement
rule.AddCondtion(po => po.NetProductPrice >= 0 )
with more dynamic one (assuming values come from XML):
rule.AddCondtion(Dynamic.GetConditiPredicate<PriceObject>("NetProductPrice", "GreaterOrEqual", "0"));

 I will discuss these possibilities in Next Post.  click here to read>>
Your comments are welcome.

Thursday, June 23, 2011

Build XML based Business Rule Engine in .NET 4 – Post 1 (Introduction and XML definition)

The requirement was simple that system should calculate Transaction Fee when distributing a product to external provider. We could have many providers and each have different rules to calculate Transaction Fee from sale price. The emphasis is on “Transaction Fee must be highly configurable”. How much *highly* configurable is contextual focus. It has to define limitation.
I thought of defining extensible lightweight XML rules so that end user can easily configure the rules or define new one of his choice. Later will develop a simple ‘Rule Engine’ to execute rules at runtime. I thought of ‘Forward Chaining’ where a distributor can have multiple rules to validate and each rule is combination of M conditions and N actions. If I could be able to have evaluated conditions - means to infer true or false at runtime then it will be achievement. Afterwards, I will go same way for actions. simple structure of a rule:

Rule => IF condition is true THEN execute action.

Let us define xml at first stage and use it at the end (its turn will not come until we are done other things for sure). 

<?xml version="1.0" encoding="utf-8" ?>
<Distributors>
<Amazon Key="Publizone_Price_Rule" Active="True">
  <Rule Name="transactionfee_is_5_percent">
    <Conditions LogicalOperation="AND">
      <Condition FieldName="NetProductPrice" Operation="GreaterOrEqual" Value="0"/>
    </Conditions>
    <Actions>
      <!--Calculate 5% of Net Price-->
      <Action Key="A1" FieldName="NetProductPrice" Operation="GreaterOrEqual" Data="5%" />
      <!--set value to TransactionFee property-->
      <Action FieldName="TransactionFee" Operation="EqualTo" Data="[A1]" />
    </Actions>
  </Rule>
  <!--If DRM is Watermarking (2) and TransactionFee < 4$ then TransactionFee = 4$-->
  <Rule Name="drm_is_watermarking">
    <Conditions LogicalOperation="AND">
      <Condition FieldName="DRM" Operation="Equals" Value="2"/>
      <Condition FieldName="TransactionFee" Operation="LessThan" Value="4"/>
    </Conditions>
    <Actions>
      <Action FieldName="TransactionFee" Operation="EqualTo" Value="4"/>
    </Actions>
  </Rule>
  <!--If DRM is hard DRMed (3) and TransactionFee < 8$  then TransactionFee = 8$-->
  <Rule Name="drm_is_digitaldrm">
    <Conditions LogicalOperation="AND">
      <Condition FieldName="DRM" Operation="Equals" Value="3"/>
      <Condition FieldName="TransactionFee" Operation="LessThan" Value="8"/>
    </Conditions>
    <Actions>
      <Action FieldName="TransactionFee" Operation="EqualTo" Value="8"/>
    </Actions>
  </Rule>
  <!--TransactionFee is always maximum 10$ -->
  <Rule Name="transactionfee_should_lessthan_10">
    <Conditions LogicalOperation="AND">
      <Condition FieldName="TransactionFee" Operation="GreaterThan" Value="10"/>
    </Conditions>
    <Actions>
      <Action FieldName="TransactionFee" Operation="EqualTo" Value="10"/>
    </Actions>
  </Rule>
</Amazon>

<iTune Key="iTune_Price_Rule" Active="True">
  <Rule Name="not_yet_defined">
  </Rule>
</iTune>
               
<GoogleBooks Key="GB_Price_Rule" Active="True">
  <Rule Name="not_yet_defined">
  </Rule>
</GoogleBooks>
</Distributors>

This is dummy a rule just as an example. Suppose Amazone will in their purchase return the following parts, per transaction.
1.       Exposed net price (i.e. the one that appears to the distributor)
2.       Transaction fee (5% of the above price, however min.  $2 by watermarked materials, min. $8 on hard DRM'ede materials, and always max. $10)
3.       "Genuine net price", ie. Exposed net price – Transaction fee

No rule definition for rest of the distributors like iTune and Google Books right now in XML.

In next post I will start building  following model (Keep it simple and stupid).

Want to read second post? click here to read>>


Your comments are greatly appreciated :