nrules

NRules: how to organize rules in modules and enable/disable with other rules


I am getting started with NRules. Here's an outline of some key requirements for the application I am building:

  1. Some of the rules determine if groups of other rules should be applied. For example (in pseudo code): "when the type of vehicle is car, apply all rules applying to cars".

  2. Some rules will determine that specific rules should be excluded.

  3. There may be many types of vehicle; ideally I don't want to load all the rules for all vehicles at start-up.

This implies that rules should be organised into modules that are enabled/disabled somehow during execution. A few solutions have occurred to me:

Is there a correct way to do what I am trying to do?


Solution

  • At the highest level, there are two ways to control what rules get applied (and even considered):

    1. Load all rules into the session and handle it within the logic of the rules
    2. Load groups of rules into different sessions, and have some sort of meta-rules that dispatch the control to the corresponding sub-set of rules.

    One of the requirements you mentioned was to not even load the rules you don't need. If this is truly the case, I think you are squarely within the second group of solutions. The engine does not have any built-in mechanisms to facilitate this scenario, so you'll have to build it yourself. As I see it, you'd need a set of meta-rules, loaded into their own session, that calculate the criteria, that you would then later use to load specific rules. For example, you would have a rule that matches a vehicle if it is a car, and inserts a "car" tag into the session. Some other meta-rules may calculate more tags. Then you would use the calculated tags to load additional rule sets, for instance, load all rules that have any of those calculated tags, and compile them into a separate session, and then run your facts against these loaded rules. The rules will need to be compiled and loaded into a new session, because once compiled, the session factory is immutable, so no new rules can be added to it.

    If you can relax your requirement of not loading all rules then you have a few more options. You already touched on some of the options within your question, but I think your best bet is to use forward chaining to achieve what you want. I personally think this is better than trying to dynamically load and compile rules.

    For example, you can have a rule that matches a Vehicle if it is a car, and yields a new fact - Car, that car-specific rules will use later on:

    Vehicle vehicle = null;
    When()
        .Match(() => vehicle, v => v.VehicleType == VehicleType.Car);
    
    Then()
        .Yield(_ => new Car(vehicle));
    

    Then, your car-specific rules will look like:

    Car car = null;
    When()
        .Match(() => car)
        //...
    

    None of the car-specific rules will even be evaluated until your rule that yields the car fact fires.