I am getting started with NRules. Here's an outline of some key requirements for the application I am building:
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".
Some rules will determine that specific rules should be excluded.
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:
Tag rules and use Agenda filters to filter the rules depending on a service that is used to enable/disable rules from being added to the agenda. I have tried this and it doesn't quite work as I would like, because a change to the service is not picked up by the engine.
Add rules that determine if rules should be applied, and use this in each rule's Match condition. This results in a lot of repeated code in each rule, which I don't like much.
Load new groups of rules during rule execution. I'm not sure how to do this, if it is recommended or should be expected to work.
Is there a correct way to do what I am trying to do?
At the highest level, there are two ways to control what rules get applied (and even considered):
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.