Introduction
In this article, we are going to see how we can apply dynamic business rules on an application without recompiling the code.
Background
My company required to build an ETL and my responsibility was to take care of the "T" Part.
You can found a brief about the DLL NxBRE here: http://sourceforge.net/projects/nxbre/. As I said I am not an expert of Rule engines, but this project might help you.
It's nice to implement BRE in your application and your intelligence to code and writing business rules will speed up your application otherwise it may hamper performance.
Using the code
You need VS 2010, this project will show you a small demo of Employee Tax and DA calculation depending on their Basic. Here is the XBRE file which contains the Rule:
="1.0"="UTF-8"
<xBusinessRules xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="xBusinessRules.xsd">
<Decimal id ="Tax" value ="10"/>
<Decimal id ="DAp" value ="10"/>
<Decimal id="5000" value="5000"/>
<Decimal id="10000" value="10000"/>
<Decimal id="15000" value="15000"/>
<Decimal id="20000" value="20000"/>
<Decimal id="30000" value="30000"/>
<Decimal id="100000" value="100000"/>
<ObjectLookup id ="EmpObj" objectId ="EmpTexCal" member ="BasicSalary"/>
<Logic>
<If>
<And>
<Between leftId ="5000" rightId ="10000" valueId ="EmpObj">
</Between>
</And>
<Do>
<Modify id ="Tax" type="Decimal" value ="11.5"/>
</Do>
</If>
<ElseIf>
<And>
<Between leftId ="10000" rightId ="15000"
valueId ="EmpObj" excludeLeft ="true">
</Between>
</And>
<Do>
<Modify id ="Tax" type="Decimal" value ="13.5"/>
</Do>
</ElseIf>
<ElseIf>
<And>
<Between leftId ="15000" rightId ="20000"
valueId ="EmpObj" excludeLeft ="true">
</Between>
</And>
<Do>
<Modify id ="Tax" type="Decimal" value ="14.5"/>
</Do>
</ElseIf>
<ElseIf>
<And>
<Between leftId ="20000" rightId ="30000"
valueId ="EmpObj" excludeLeft ="true">
</Between>
</And>
<Do>
<Modify id ="Tax" type="Decimal" value ="15.5"/>
</Do>
</ElseIf>
<ElseIf>
<And>
<Between leftId ="30000" rightId ="100000"
valueId ="EmpObj" excludeLeft ="true">
</Between>
</And>
<Do>
<Modify id ="Tax" type="Decimal" value ="17"/>
</Do>
</ElseIf>
</Logic>
<Logic>
<If>
<And>
<Between leftId ="5000" rightId ="10000" valueId ="EmpObj">
</Between>
</And>
<Do>
<Modify id ="DAp" type="Decimal" value ="25"/>
</Do>
</If>
<ElseIf>
<And>
<Between leftId ="10000" rightId ="15000" valueId ="EmpObj" excludeLeft ="true">
</Between>
</And>
<Do>
<Modify id ="DAp" type="Decimal" value ="30"/>
</Do>
</ElseIf>
<ElseIf>
<And>
<Between leftId ="15000" rightId ="20000" valueId ="EmpObj" excludeLeft ="true">
</Between>
</And>
<Do>
<Modify id ="DAp" type="Decimal" value ="40"/>
</Do>
</ElseIf>
<ElseIf>
<And>
<Between leftId ="20000" rightId ="30000" valueId ="EmpObj" excludeLeft ="true">
</Between>
</And>
<Do>
<Modify id ="DAp" type="Decimal" value ="50"/>
</Do>
</ElseIf>
<ElseIf>
<And>
<Between leftId ="30000" rightId ="100000" valueId ="EmpObj" excludeLeft ="true">
</Between>
</And>
<Do>
<Modify id ="DAp" type="Decimal" value ="60"/>
</Do>
</ElseIf>
</Logic>
<ObjectLookup id="ExpDAp" type="NxBRE.Imp.Util.Evaluator,NxBRE.Imp" member="CreateExpression">
<Argument value="(" />
<Argument valueId="EmpObj" />
<Argument value="/" />
<Argument value="100" />
<Argument value=")" />
<Argument value="*" />
<Argument valueId="DAp" />
</ObjectLookup>
<ObjectLookup id="EvalDAp" type="NxBRE.Util.Compilation" member="Evaluate">
<Argument valueId="ExpDAp" />
</ObjectLookup>
<ObjectLookup id="CastDAp" type="NxBRE.Imp.Util.Casting,NxBRE.Imp" member="CastData">
<Argument valueId="EvalDAp" />
<Argument value="System.Decimal" />
</ObjectLookup>
<ObjectLookup id ="EmpObjOut" objectId ="EmpTexCal" member ="DA">
<Argument valueId="CastDAp"/>
</ObjectLookup>
</xBusinessRules>
Here is how to read the XBRE file, I will explain the XBRE file later:
public ReadRule(string RuleFilePath)
{
_FilePath = RuleFilePath;
StringBuilder sb=new StringBuilder(255);
if (GetShortPathName(_FilePath, sb, sb.Capacity) != 0)
_factory = new BREFactoryConsole(_engineTraceLevel, _ruleBaseTraceLevel).NewBRE(new XBusinessRulesFileDriver(sb.ToString()));
}
You can change trace level to get trace result. I have two separate concerns: one is reading rule from a file and setting values for depending calculations.
Set value of depending variables. Set value of a particular object.
public void SetObjectRefrance(ReadRule objRule, string ObjectId, object Obj)
{
if (objRule.IsValidXML)
{
if (objRule.IsProcessed)
objRule.Reset();
objRule.Factory.RuleContext.SetObject(ObjectId, Obj);
}
}
We can set any delegate of ExecuteRuleDelegate
type through which we can call back our base code while performing rules.
public void SetDelegateRefrance(ReadRule objRule, string ObjectId, ExecuteRuleDelegate Delegate)
{
if (objRule.IsValidXML)
{
if (objRule.IsProcessed)
objRule.Reset();
objRule.Factory.RuleContext.SetFactory(ObjectId, new BRERuleFactory(Delegate));
}
}
In this rule engine we can split our rule concern and perform partial rule or we can perform the rule but fetch only partial result values.
* We have to set depending values or the Delegate.
public object GetRuleResult(ReadRule objRule, string ObjectId)
{
try
{
if (!objRule.IsProcessed)
objRule.ProcessRules();
return objRule.Factory.RuleContext.GetResult(ObjectId).Result;
}
catch (Exception)
{
}
return FlagResualt.NoProcessed;
}
Now I have created a combine function which will take care of all of the above functions:
public void EvaluateRule(ReadRule objRule, RuleRefrance[] RulesReferances,
RuleDelegate[] RulesDelegates, RuleResualt[] RuleReply)
{
if (RulesReferances != null)
AddObjects(objRule, 0, RulesReferances);
if (RulesDelegates != null)
AddDelegates(objRule, 0, RulesDelegates);
if (!objRule.IsProcessed)
objRule.ProcessRules();
GetObjectResult(objRule, 0, RuleReply);
}
GetObjectResult
just sets values at our ORM or any other class using Reflection.
Now how to call all of this and get the rule output, it's very simple: