The BDRuleEngine Framework provides a rule system for Cocoa developers to use when creating applications. The BDRuleEngine Framework's rule system lets you (a) specify business rules separately from your application code, (b) specify the context within which rules are evaluated while your application is running, and (c) ask the rule system questions that it will answer depending on its current context.
The rule system is completely general and quite flexible. There are three basic components to the rule system: Rule models, rules, and rule contexts. Rule models are collections of rules; any number of rule models can be used at one time. Rules consist of a qualifier (the left-hand side) that tells whether the rule can fire, and an assignment that is used to determine the result of firing a rule. Rules also have priorities, which can be used to help select among various rules that may apply in a given situation. Rule contexts are specific contexts within which rules are evaluated; they are associated with a model and user-supplied data, and are your application's primary interface with the rule system.
The BDRuleEngine Framework is built on the Open Source BDControl Framework, also available from bDistributed.com.
The BDRuleEngine Framework is released as Open Source under a BSD license by bDistributed.com, Inc. Please see http://bdistributed.com/Projects/BDRuleEngine.html for the latest source code and binary release.
The rule system provided by the BDRuleEngine framework provides a generic, data-driven mechanism for codifying business rules and evaluating them within a particular context. Business rules may be specified programmatically or in ruleset files. Rules are not evaluated directly, but are accessed via rule contexts that contain supporting data and a link to a particular rule model.
To get data out of the rule system once a rule model and rule context have been created, send a rule context -valueForKey:
or -valueForKeyPath:
. This will cause it to select all candidate rules whose right-hand side key path matches the leftmost key in the supplied key path. The candidate rules will be ordered by their priority and then by the number of qualifier keys in their left-hand side. Then each candidate rule's left-hand side will be evaluated against the context, and the first candidate rule whose left-hand side returns YES
will have its right-hand side fired. The result of the firing is the result returned to the sender.
A business rule has the following form:
(condition) => key path = value [priority]
The left-hand side condition is an instance of BDQualifier (from the BDControl Framework). It can be an arbitrary qualifier expression so long as it does not contain qualifier variables.
The right-hand side key path and value constitute an assignment, represented by an instance of BDAssignment.
The priority is a numeric ranking that is used to differentiate among rules that may apply in a given situation.
Say you have the following business rules in a rule model colorRuleModel
for determining the hue angle of colors:
(color = 'red') => hue = 0 [0] (rule 1)
(color = 'green') => hue = 120 [0] (rule 2)
(color = 'blue') => hue = 240 [0] (rule 2)
To use these rules to find the hue angle of a given color, you first create a context referencing that model:
BDRuleContext *colorContext;
...
colorContext = [[[BDRuleContext alloc] initWithModel:colorModel] autorelease];
You then tell that context about the information you currently have:
[colorContext takeValue:[myobject colorName] forKey:@"color"];
Finally, you ask the context for the information you want:
NSNumber *hue;
...
hue = [colorContext valueForKeyPath:@"hue"];
This causes the context to first see if it has a stored value for the "hue" key. If it doesn't, it asks the rule system for all rules with "hue" as their right-hand side key path. It sorts these in priority order, and then by the number of qualifier keys on their left-hand side. Finally, it goes through them one at a time evaluating their left-hand side until it finds one whose left-hand side evaluates to YES
. It then fires the rule's right-hand side assignment, and returns the result of that firing.
So if [myObject colorName]
returns "green", rule 1's left-hand side will be evaluated first and evaluate to NO
. Then rule 2's left-hand side will be evaluated next and evaluate to YES
. Rule 2's right-hand side will fire because of this, and the number "120" will be returned. Thus the value of the hue
variable will be set to an NSNumber of 120 in this case.
Note that the rules' left-hand sides are evaluated against the context, so if there is no stored value for the "color" key in this example, evaluating each rule's left-hand side will attempt to use the rule system to find the value of the "color" key.
Most developers won't need to extend the rule system due to its generality. However, many developers may have some use for custom assignments. Custom assignments are subclasses of BDAssignment that override -[BDAssignment fireInContext:]
to return a different value, possibly based on the value set for the assignment.
Two custom assignments, BDKeyAssignment and BDPropertyListAssignment, are included with the BDRuleEngine Framework. The former treats its value as a key path within the context, and returns the value at that key path within the context as its result. The latter treats its value as the string representation of a Cocoa property list type (array or dictionary) and returns an instance of NSArray or NSDictionary as its result.
Collections of rules may be stored in ruleset files. As of version 1.1, the BDRuleEngine framework can both load and save BDRuleModels to these files. (Previous versions could only load ruleset files, and only an older version of the file format.) This loading and saving is accomplished internally via an informal protocol named BDPropertyListEncoding. Or you can use a higher-level API in BDRuleModel, -[BDRuleModel initWithContentsOfFile:]
for loading and -[BDRuleModel writeToFile:atomically:]
for saving.
These ruleset files are formatted as standard Cocoa property list files. As such, they can be edited by hand in a text editor or they can be edited at a higher level using the Property List Editor application. They can also be edited at a high level using the BDRuleEditor application available from bDistributed.com.
Note: The format of ruleset files has changed from version 1.0 to 1.1. All future development should use the new file format. The methods -[BDRuleModel initWithRulesFromFile:]
and -[BDRuleModel initWithRulesFromPlistRep:]
were renamed to -[BDRuleModel initWithRulesFromOldStyleFile:]
and -[BDRuleModel initWithRulesFromOldStylePlistRep:]
, respectively, and will continue to work. However, new development should use instead either -[BDRuleModel initWithContentsOfFile:]
or -[BDRuleModel initWithPropertyList:owner:]
and -[BDRuleModel awakeWithPropertyList:]
to initialize a rule model with the contents of a ruleset file.
No caching of results is performed at this time. This means that with a lot of rules in a model, rule selection and evaluation could be slow.
There is no customization of the rule system for developers of desktop applications. In other words, there are no predefined rules for desktop application developers, and there are no convenience accessors in BDRuleContext for desktop application developers. This is a conscious choice. If a standard set of rules for desktop applications is created, they will be separate from the rule system. And if convenience accessors are desired, they will not be added to BDRuleContext but rather to a subclass.