Giter Site home page Giter Site logo

regla's Introduction

Regla

A simple rules engine written in C#

Architecture

Regla has 3 core Module

  1. Rule Module
  2. Engine Module
  3. Result Module

Rule

Rule encapsulates a method to call and its attributes

  • Rules can be given names to identify them
  • Rules can be part of a group
  • Rules can specify whether to stop on failure or exception
  • Rules can be composed of AndRules or OrRules for short circuiting

RuleMethod can be provided in 3 forms

  1. A delegate function
  2. A lambda expression
  3. A class inherited from Rule class
Class Name Description
RuleMethod Encapsulation of a delegate or Func<>
RuleAttributes Various options for the rule
Rule Class which hold RuleMethod and RuleAttributes
AndRule Rule short-circuiting until a rule fails
OrRule Rule short-circuiting until a rule succeed

Engine

Class Name Description
EngineAttributes Various options for the Rules Engine
RulesEngine Holds EngineAttributes and Rules List

Result

Class Name Description
RunResultAttribute Result of Rules execution
RuleResultAttribute Result of individual rule execution
Result Holds RunResult and RuleResult Attribute classes

Basic flow

1. Create an instance of RulesEngine

Pass EngineAttributes as constructor parameter or set properties after instantiating

2. Add Rules to engine

Either create Rules before instantiating the engine and pass them as constructor parameter or use AddRule() functions to add them after creating the engine

3. Run the engine

The Engine can run in different modes e.g. RunAllRules, RunNamedRules, RunGroupRules.

Sample code

Simplest RuleEngine

  1. Create an empty RuleEngine
  2. Add a Rule (we are using Lambda here)
  3. Run the rules
  4. Inspect Result graph
class RulesEngineDemo
{
    static void BasicExample()
    {
        // Create an empty RulesEngine
        var myRules = new RulesEngine();

        // Add a lambda rule to the engine
        myRules.AddRule(new Rule((component, output) => { Console.WriteLine("Hello, world"); return true; }));

        // Run the engine, it will print Hello, world
        var result = myRules.RunAllRules();
        // Hello, world

        // Result class contains detailed object graph, which can be accessed thru properties
        // It's ToString() returns a Json string
        Console.WriteLine(result);

    }
    public static void Main()
    {
        BasicExample();
    }
}
/* The output
Hello, world
"Result": {
  "EngineAttributes": {
    "Name": "Engine_1",
    "StopOnException": true,
    "StopOnRuleFailure": false,
    "Component": null,
    "Output": null
  },
  "RunResultAttributes": {
    "TotalRulesCount": 1,
    "RulesExecutedCount": 1,
    "ExecutionType": "All",
    "FailedRuleName": null,
    "FailureReason": null
  },
  "RuleResultAttributes": [
    {
      "Rule": {
        "RuleMethod": "<BasicExample>b__3_0",
        "RuleAttributes": {
          "Name": "<BasicExample>b__3_0",
          "Group": "default",
          "Enabled": true,
          "StopOnException": true,
          "StopOnRuleFailure": false
        }
      },
      "ReturnValue": true,
      "Exception": null
    }
  ]
}*/

While the output looks very detailed, we don't have to worry about it yet. The Result object graph can be used to analyze and debug our RulesEngine

Different ways of specifying rule

Rules can be delegates, Func<>, lambda or even classes

class RulesEngineDemo
{
    // Define a rule delegate method
    static bool ruleDelegate(object component, object output)
    {
        Console.WriteLine("ruleDelegate called");
        return false;
    }

    static void DelegateExample()
    {
        // Create EngineAttributes with Name
        var engineOptions = new EngineAttributes { Name = "MyEngine" };

        // Instantiate the RulesEngine with option
        var myEngine = new RulesEngine(engineOptions);

        // Now add Lambda rule
        myEngine.AddRule(new Rule((c, o) => { Console.WriteLine("Hello, world"); return true; }));

        // Next, add a delegate method
        myEngine.AddRule(new Rule(ruleDelegate));

        myEngine.RunAllRules();
        // Output:
        // Hello, world
        // ruleDelegate called
    }

    public static void Main()
    {
        DelegateExample();
    }
}

As we have seen, we can easily pass a method or lambda to the Rules Engine, but to do complex processing, we may require a class with parameters and multiple functions

class RulesEngineDemo
{
    /**
     * Sample Component for RulesEngine to work on
     */
    class Customer
    {
        public int ID { get; set; }
        public string Name { get; set; }
        public DateTime Birthdate { get; set; }
        public DateTime FirstPurchase { get; set; }
    }

    /**
     * Sample "Output" object, which rules can use for writing data
     */
    class CustomerDiscount
    {
        public decimal Discount { get; set; };
    }

    /**
     * Rules can be written as methods, lambdas or even classes
     * Class rules allow us to pass constructor params, which can be used later
     * e.g.
     *      engine.AddRule(new LoyaltyDiscount(1, 0.05));
     *      engine.AddRule(new LoyaltyDiscount(5, 0.08);
     *      engine.AddRule(new LoyaltyDiscount(10, 0.10));
     */
    public class LoyaltyDiscount : Rule
    {
        public int Years { get; private set; }
        public decimal Discount { get; private set; }

        // We can set RuleAttributes by calling base Rule constructor
        public LoyaltyDiscount(int years, decimal discount)
        {
            // We can also set the RulesAttributes inside the constructor
            base.RuleMethod = CalculateDiscount;
            Years = years;
            Discount = discount;
        }

        /**
         * The actual rule can be called anything, but must have matching signature
         */
        public bool CalculateDiscount(object input, object output)
        {
            var customer = (Customer)input;
            var customerDiscount = (CustomerDiscount)output;
            if ((DateTime.Today - customer.FirstPurchase).Days / 365 > Years)
                customerDiscount.Discount = Math.Max(customerDiscount.Discount, 50);
            return true;
        }
    }

    static void RuleClassExample()
    {
        /**
         * Create the component to initialize the RulesEngine
         * We don't HAVE to have a component, the rules can still run
         */
        var bigB = new Customer
        {
            ID = 1,
            Name = "Amitabh",
            Birthdate = new DateTime(1942, 10, 11),
            FirstPurchase = new DateTime(2005, 1, 2),
        };

        /**
         * Create an output object for Rules Engine
         * We don't HAVE to have an output object
         */
        var discount = new CustomerDiscount();

        // Create Engine Attributes
        var engineAttributes = new EngineAttributes { Component = bigB, Output = discount };

        // We can add individual rules or an array of rules
        var rulesArray = new Rule[] { new Rule(seniorCitizenDiscount, ruleName: "Senior") };

        // Start the engine with attributes and initial rules list
        var engine = new RulesEngine(engineAttributes, rulesArray);

        // We are adding multiple rules based on the same class, so we must name them explicitly
        engine.AddRule(new Rule(new LoyaltyDiscount(1, 5), ruleName: "L1"));
        engine.AddRule(new Rule(new LoyaltyDiscount(5, 8), ruleName: "L2"));
        engine.AddRule(new Rule(new LoyaltyDiscount(10, 10), ruleName: "L3"));

        Console.WriteLine(engine.RunAllRules());

        // Now print the discount
        Console.WriteLine(discount);
        }
    }

    public static void Main()
    {
        RuleClassExample();
    }
}

Engine options

EngineAttributes has many options to control the engine

Attribute Description
Name User defined name for the engine
Component Object on which the rules will execute
Output Object will can be used for rules to store values
StopOnException Stop rules processing if execution is throw (default: true)
StopOnRuleFailure Stop rules processing if a the current rule returns false (default: false)

Rules

Rule encapsulates the user provide method (or class object) and holds its attributes A rule can have a user defined name and/or a group, which can be used to run named rules or run group rules. These names can also be used for removing rules dynamically

:
:
// Define a new rule using a delegate method
var seniorRule = new Rule(seniorCitizenDiscount, name:"senior", group:"discount", stopOnException:true, stopOnRuleFailure:true);

// Add another rule for birthday
var birthdayRule = new Rule(birthdayDiscount, group:"discount");

// Add a rule for seasonal discount (not part of group:discount)
var seasonalDiscount = new Rule(new SeasonDiscount(), group:"seasonal"));

// Instantiate the engine with component and output
var engine=new RulesEngine(component:customer, output:discount, name:"CustomerRule");

// Run only those rules which are part of "discount" group
var groupResult = engine.RunGroupRules("discount");

// Check the discount here
Console.WriteLine("Customer: {0} got {1} discount", customer.Name, discount);

// We can also run named rules
var namedResult = engine.RunNamedRules(new string[] {"senior", "seasonal"});

// We can remove individual rules or grouped rules
engine.RemoveRule("senior");            // By name
engine.RemoveRule(birthdayDiscount);    // By delegate
engine.RemoveRule(seasonalDiscount);    // By Rule reference
engine.RemoveGroup("discount");         // By Group name

// Or we can clear all rules
engine.clearRules();

Remarks

Regla is WIP (Work In Progress) and the architecture and implementation will change with feedback. You may use this code to understand the architecture and implementation but are strongly advised against using this library for any production work.

regla's People

Contributors

sanjayvyas avatar

Watchers

James Cloos avatar

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.