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:
string RuleFilePath = "EmployeeTexCalRule.xbre";
ReadRule objRead = new ReadRule(RuleFilePath);
NxBRE.Imp.Implementation.Execution exec = new Imp.Implementation.Execution();
txtOut.Text = "";
var list = new DataAccess.DataAccessLayer().GetAllEmployeeDetails();
foreach (BusinessObjects.BO.EmployeeBO item in list)
{
exec.EvaluateRule(objRead, new[] { new RuleRefrance() { ObjectId = "EmpTexCal", Object = item } }, null,
new[] { new RuleResualt() { ObjClass = item, ObjectId = "Tax", PropertyName = "TaxPercentage" },
new RuleResualt() { ObjClass = item, ObjectId = "EvalDAp", PropertyName = "DA" } });
txtOut.Text += String.Format("Name : {0} BasicSalary : Rs.{1} Tax : {2}% DA : Rs.{3}",
item.Name, item.BasicSalary, item.TaxPercentage, item.DA) + Environment.NewLine;
}
Output:
Name : Suvabrata Roy BasicSalary : Rs.50000 Tax : 17% DA : Rs.30000
Name : Arindam Jash BasicSalary : Rs.16000 Tax : 14.5% DA : Rs.6400
Name : Riya Debnath BasicSalary : Rs.8000 Tax : 11.5% DA : Rs.2000
Name : Sanjoy Debnath BasicSalary : Rs.25000 Tax : 15.5% DA : Rs.12500
Name : Sraboni Roy BasicSalary : Rs.10000 Tax : 11.5% DA : Rs.2500
Name : Ahsan Ali BasicSalary : Rs.12000 Tax : 13.5% DA : Rs.3600
Name : Pritam Shaw BasicSalary : Rs.12000 Tax : 13.5% DA : Rs.3600
Name : Rimi Saha BasicSalary : Rs.7000 Tax : 11.5% DA : Rs.1750
Now you need to know about the XBRE XML file:
Variables
<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"/>
Basic Object: we will set employee object to it.
<ObjectLookup id ="EmpObj" objectId ="EmpTexCal" member ="BasicSalary"/>
Logic of tax calculation:
<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>
Here is the code to set a value and execute a rule.
foreach (BusinessObjects.BO.EmployeeBO item in list)
{
exec.EvaluateRule(objRead, new[] { new RuleRefrance() { ObjectId = "EmpTexCal", Object = item } }, null,
new[] { new RuleResualt() { ObjClass = item, ObjectId = "Tax", PropertyName = "TaxPercentage" },
new RuleResualt() { ObjClass = item, ObjectId = "EvalDAp", PropertyName = "DA" } });
txtOut.Text += String.Format("Name : {0} BasicSalary : Rs.{1} Tax : {2}% DA : Rs.{3}",
item.Name, item.BasicSalary, item.TaxPercentage, item.DA) + Environment.NewLine;
}
Points of Interest
It is not the final project, and I apologies for not describing all the parts, but as I have time I will update this project and code.
Upcoming Features
- Apply rule on DataTable columns.
- DataTable to property data mapping and vice versa.
- How to manage multiple Rule files (Factory Pattern)
- One nice IDE where we can drag and drop to create BR (Business Rule). (It will take some time to build :-))
Some more ...