An Extensible Light Xml Rules Engine Component

he Extensible light XML rules engine component allows the easy configuration of rules in XML, definition of the interpretations of rules and the actions to be taken by client code. Though the component idea is simple its extensibility and completeness makes it worth of reuse. The component is developed in C# and .Net framework.

Component Concept:

A rule is defined in Webster Dictionary as “a prescribed guide for conduct or action”. In that sense the rules engine purpose is to take a certain action based on a guide. The guide consists of one or more condition and the current state of an object. The conditions can be group with various Boolean operations such as OR, AND, XOR and NOT etc. The combinations can get as complex as you can imagine. What about the action? The action would perform certain behavior based on the data the guide used. In that sense the rules engine as the plural of the rules indicates, contains a number of rules. The engine would check if a rule applies to a certain data object and if so to execute the action associated with that rule guide. For example the rules engine would allow us to formulate rules for entering a club like the ones bellow

* If Age > 18 then Allow to enter the club
* If Age > 18 and Name is Al then Enter the VIP section of the club.

Other kind of rules can be defined like the following generic rules
* If (Condition) then action
* If (condition1) and (Condition2) then action
* If (Condition1) or (Condition2) then action
* If ((Condition1) and (Condition2)) or ((condition3) and (Condition4)) then action
* If ((Condition1) or (Condition2)) and ((condition3) or (Condition4)) then action

Now how do we define the rules and the associated actions? We need to be able to change the rules definitions easily and extend the various guides that the rules use. These to requirements can be satisfied with XML and .Net Reflection technology. This is the approach that I took for the design and implementation of this component.

Requirements

Let’s convert the previous section into specific software requirements and let’s expand it to define the various unplanned actions “exceptions” that might occur as follows:

1. The component can be easily incorporated into other software be it managed code or unmanaged code.
2. The rules should be easily defined and changed without having to rewrite code.
3. The guides can easily be defined and the rules engine handling of different conditions (guides) can be easily enriched by the designer using the component.
4. The actions can easily be defined by the designer using the component.
5. The component should be robust and able to check the validity of the rules supplied.

Detailed Requirements

We can elaborate the requirements definition to:

1. The component would take an object as an input and applies the rules to this object and returns an action object.
2. The Action object exposes a method (Execute) that takes as an input an object and returns a Boolean value that indicates success or failure of the action.
3. The component would read the rules from an XML.
4. The logic used to evaluate the rules would be easy to extend on the fly.
5. Allows for easily configuration through an XML file.
6. The Component would apply the rules sequentially till one handles this object data. It then returns an Action object that is associated with the rule.
7. The component can be used from unmanaged code as well as managed code.
8. In case there is no rule that handles the current object data the component would throw a NonHandledException Exception.
9. If the rules File does not confirm to the schema then the rules engine would throw and exception BadRulesFileException
10. If the developer tries to apply rules to before loading the rules it would throw UnLoadeRulesException
11. The user of the component can use it like
IAction Action = rulesEngine.ApplyRules(prsn);
Action.Execute(prsn);
Or

rulesEngine.ApplyRules(prsn).Execute(prsn);

Rules Engine Component Detailed Design
Component Architecture

Figure 1: Component Architecture.

The Rules Engine component mainly consists of two DLLs, one for the functionality and one for the interfaces. The Fields and Actions DLLs can be combined into one DLL however for demonstrations purposes they have been separated in the sample. Through reflection the Rules engine gets access to the Fields and Actions DLLs. The XML rules file determines which assemblies would be loaded and the classes to instantiate for each field or action.
The kind of comparisons the rules engine can work with would be >, < , >=, <= .= Starts With, Ends With and Includes. These comparisons seems to cover every possible comparison the can be made based on numerical or textual information, though there interpretations would be defined by the client code.
Object Model

Figure 2: Object Model.

The rules engine class is the main class. It contains a rule class. The rule class consists of a list of conditions and an Action. The Conditions consists of several condition classes joined together by a logical condition (OR, AND). Each condition contains a field an enumeration that is preformed on that field. The Field objects implements IField interface described later. The Action class implements the IAction interface described later. Bellow is a simple description of the different classes in the diagram.

Class Description
RulesEngine The RulesEngine class is the main class of the component. Client code creates an instance of this class to get access to the component functionality. The constructor takes as argument the name (including the path) of the XML rules file. The RulesEngine class is the only class exposed by the RulesEngine component
The ApplyRules method is the only method that this class exposes. It return a IAction interface. Or throws either NonHandledException or UnLoadedRulesException

 

Rule The rule class is an internal class. It represents a single rule. The constructor accepts an XMLNode and parses it to build the rule
LogicalCondition This is an internal abstract class that is the base for the Conditions and Condition Classes.
Conditions This is an internal class. It represents a complex condition. A complex condition consists of several condition(s)
Condition This is an internal class. This represents a single condition. A condition can be one of
“StartsWith”

“EndsWith”

“Includes”

“Greater”

“GreaterOrEqual”

“Less”

“LessOrEqual”

A condition Has a field object which implements IField interface.


IAction Interface

The IAction Interface defines the action that a rule would perform provided that it guide is fulfilled. The interface is described n detail bellow:

Method/Property

Description

Initialize This method initializes the action. The action might require extra information that can be passed through a string and parsed there
Execute This the most important part of an Action. It performs the action as its name depicts. It takes as an argument the same object that the rule has checked for validity. It returns a Boolean to indicate success or failure
Name Property The name property is provided in case the client code needs to refer in the diagnostics or tracing to the action name in a human readable format. The Name would be set in initialize method.
Id property The Id property is provided in case the client code needs to perform different code depending on the action chosen by the rule to prepare for the action such as opening a database connection or establishing a connection with another server. Though I would advise that such code should be included in the Execute method or called from there.

IField Interface

The IField interface is the main working horse of the component. The implementer would determine how to interpret the various operations. I.e. what does Greater mean to your objects. The various methods are described bellow.

Method/Property

Description

Initialize This method initializes the Field. Mainly the default value that would be compared to is passed here. Other initializations can be preformed here too.
Greater Compares the passed object to the default value of the field and returns true if it is greater.
Less Compares the passed object to the default value of the field and returns true if it is less.
GreaterOrEqual Compares the passed object to the default value of the field and returns true if it is greater or equal.
LessOrEqual Compares the passed object to the default value of the field and returns true if it is less or equal.
Equal Compares the passed object to the default value of the field and returns true if it is Equal.
StartWith Compares the passed object to the default value of the field and returns true if it Starts with the value. Mainly this is used for string objects or objects with lists
EndWith Compares the passed object to the default value of the field and returns true if it ends with the value. Mainly this is used for string objects or objects with lists
Includes Compares the passed object to the default value of the field and returns true if it includes the value. Mainly this is used for string objects or objects with lists

Dynamic Model

Figure 3:Sequance Diagram.

The dynamic model is simple as after instatiation the client code would call Apply rules on the RulesEngine which would go through the rules apply each one till one returns an object that implements IAction which it passes to the client code. The client code would then call the execute method on the IAction. Bellow is a simple sequence diagram that shows this process.

Data Model “Rules XML Schema”

The Full XML Schema is located at..\Dev\RulesEngine\RulesSchema.xsd. It is too long to include in this document. I will point out a couple of important points.

Figure 4: Rules Schema

As you can see from the figure above the XML rules schema consists of a Rules node (the root node) which contains multiple Rule Nodes. A Rule node contains an Action Node and a one or more Conditions Nodes. An Action node as in the Schema fragment bellow has three attributes.

<xsd:complexType name=”ActionType”>
<xsd:attribute name=”ActionType” use=”required” />
<xsd:attribute name=”ActionAssm” use=”optional” default=”” />
<xsd:attribute name=”Data” use=”required” />
</xsd:complexType>

The ActionType attribute is the name of the class that the rules engine would instantiate. The ActionAssm is the name of the assembly that contains the class defined in the ActionType attribute. The Data attribute contains the initialization string that would be passed after instatianting the class to the IActioodn:Initialize method.

Figure 5: Conditions detailed schema
Figure 5 shows the Condition schema. The important point to notice here is that the field type and FieldAssm which is similar to the ActionType and ActionAssm explained above. Also notice the definitions of the operation attribute with the enumeration type to be able to validate the correct definition of different operations.

<xsd:complexType name=”ConditionType”>
<xsd:attribute name=”FieldName” use=”required” />
<xsd:attribute name=”FieldType” use=”required” />
<xsd:attribute name=”FieldAssm” use=”optional” default=”” />
<xsd:attribute name=”Operation” use=”required”>
<xsd:simpleType>
<xsd:restriction base=”xsd:string”>
<xsd:enumeration value=”StartsWith” />
<xsd:enumeration value=”EndsWith” />
<xsd:enumeration value=”Includes” />
<xsd:enumeration value=”Greater” />
<xsd:enumeration value=”GreaterOrEqual” />
<xsd:enumeration value=”Less” />
<xsd:enumeration value=”LessOrEqual” />
</xsd:restriction>
</xsd:simpleType>
</xsd:attribute>
<xsd:attribute name=”Value” type=”xsd:string” use=”required” />
<xsd:attribute name=”LogicalOperation” use=”optional” default=”AND”>
<xsd:simpleType>
<xsd:restriction base=”xsd:string”>
<xsd:enumeration value=”OR” />
<xsd:enumeration value=”AND” />
<xsd:enumeration value=”NOT” />
</xsd:restriction>
</xsd:simpleType>
</xsd:attribute>
</xsd:complexType>

Multi-Threading Support

The Rules engine gets initialized once and then can be used as many times. There is no dependency on object states between calls to ApplyRules. So there are no worries about simultaneous calls corrupting each other. The one thing the client code would need to protect is the object it sends to the ApplyRules method. Though I could have locked it as soon it entered to ApplyRules that measure did not make sense as the synchronization has to be though in the whole application context, so this is left to the client code to figure out.

Remoting Support

While a remoting scenario for the rules engine seems a far fetched one it is still possible that some client code would want to imply it that way. The RulesEngine class is implemented to inherit from MarshalbyRefObject so it can be marshaled by reference. Also there is IRulesEngine interface that exposes only ApplyRules. The reason I choose to marashal it by reference is that there is no sense in marshaling it by value as if you need to marshal it by value then all you have to do is send the xml rules file and schema to remote code. The exceptions are implemented to support remoting. The IAction interface remoting would depend on the object which implements it and your distributed system design. I provided the main functionality here but you will have to figure out how the Rules Engine would fit within your distributed system.
COM Support
The rules engine is exposed as a COM object with GUID {ECD78EB8-FF0E-4f36-A62F-93A6EAC7D1BB}. The Apply rules is exposed as MTA model to simplify the threading mode.

The Sample and source code

Attached with this article is the source code. The sample is a simple dialog through which you point the rules engine to the rules xml file (normally located at ..\Dev\RulesEngine\SampleRules.xml) and then you can run simple tests. The sample defines an Object called person which you enter the name and age. The rules determine if that person can enter the club, should go to the VIP section or should not enter the club at all. It is a simple rules but it explains the idea. There are two DLLs that define the various comparisons in the Fields.DLL and the Action is a simple implementation that just displays a dialog box with the rule message.

Advertisements