In the "What Is a Business Rule Engine?"
section, we touched briefly on the structure of a business rule and the
fact that it is composed of facts (no pun intended), conditions, and
actions (see Figure 2). In this section, we give you a detailed look at these artifacts.
4.1. Facts and Vocabularies
Before embarking on the creation of business rules,
the business analyst should identify the different data facts involved
in the evaluation or execution of a particular rule. Those facts are
usually aliased using a domain-specific nomenclature understood by peers
in that domain. Such domain-specific definitions are referred to in the
Business Rule Composer as a vocabulary, as in the vocabulary specific to the problem domain for which you are creating the business rules.
Vocabularies are a wonderful way to abstract facts
from their implementation. Although a vocabulary set can be composed of
different types of facts, the business analyst and business rule creator
can deal with them all in the same fashion while creating or updating
their business rules.
These types of facts can be included when composing a vocabulary:
Constant values, ranges of values, or value sets used to validate and constrain rule parameters.
.NET
classes or class members, which may be used to wrap other vocabularies
and/or define bindings and binding parameters. "For example, a
vocabulary definition might refer to a .NET method that takes two
parameters. As part of the definition, one parameter may be bound to the
value in an XML document and the other may be defined by the user at
design-time but limited to the valid values defined in another
definition that defines a 'set'".
XML document elements or attributes.
Data tables or columns in a database.
Custom managed facts to be retrieved through a .NET object that implements the fact retriever interface.
Using the Business Rule Composer, you may define
vocabularies and store them in the shared rule store. Vocabularies can
also be consumed by tool developers responsible for integrating rule
authoring into new or existing applications.
NOTE
The requirement to define vocabularies and facts
before the definition of the business rules within a policy can be
cumbersome and annoying for most business analysts, and usually results
in them creating and publishing multiple versions of their vocabularies
as they are developing the business rules. Hopefully in the future, the
Business Rule Composer will allow the composition of business rules
based on unpublished vocabularies that business analysts can create and
edit while creating their rules, and restrict users from publishing
policies until the vocabulary they use is published.
Before being used in business rules, vocabularies
must be stamped with a version number and published in the rule store.
Once published, a vocabulary version is immutable. This guarantees that
the definitions in the vocabulary will not change. It preserves the
referential integrity between policies and the vocabulary. This prevents
any policies that use a particular version of the vocabulary from
failing unexpectedly due to changes in the underlying vocabulary .
Users can define two types of vocabulary items or facts, short-term and long-term
facts. A short-term fact is specific to a single execution cycle of the
Business Rule Engine and does not need to exist beyond that execution
cycle. A long-term fact is loaded into memory for use over an arbitrary
number of execution cycles. Long-term facts are used to represent static
datasets or data that changes infrequently, such as a list of U.S.
states or daily interest rates.
In the BizTalk BRE, the only real distinction between
the two is in implementation Long-term facts improve performance by limiting queries to source
repositories and should be considered for high-performing applications.
To use long-term facts, you must configure your
policy to know where to find them and implement a fact retriever object
that can fetch those facts from an external store and present them to
the policy. According to the BizTalk Server 2009 documentation
(Microsoft, 2009), there are three ways to supply fact instances to the
rule engine:The simplest way to submit fact instances to
the rule engine is with short-term, application-specific facts that you
pass in to the policy object as an array of objects or as XML messages
from an orchestration at the beginning of every execution cycle.
You can use your rule actions to assert additional facts into the engine during execution when those rules evaluate to true.
You
can write a fact retriever—an object that implements a standard
interface and typically uses them to supply long-term and slowly
changing facts to the rule engine before the policy is executed. The
fact retriever may cache these facts and use them over multiple rule
execution cycles.
NOTE
If your data changes frequently between execution
cycles and must be reinstantiated and asserted again, you likely want
to represent this data as short-term facts.
4.1.1. XML Fact Strategies
As we mentioned previously, facts asserted into the
rule engine's working memory can be .NET objects, XML documents, or data
tables. These facts contain fields called slots in the world of rule
engines. If a business rule requires access to a slot in a fact to
evaluate a condition and that slot is not defined—for example, an
optional XML element that is not defined in the XML input message—the
BRE will throw an exception. The engine will attempt to perform this
evaluation because the relevant fact has been asserted. However, when it
looks for the slot, it will not find it.
In this situation, why does the engine throw an exception?
When you create a vocabulary definition for a node in
your schema or when you use an XML fact directly in the rule, two
properties are set: XPath Selector and XPath Field.
These properties are the way the engine can refer to data fields or
slots in a given fact. The vocabulary definition maps these to
business-friendly terms defined by the Name and Display Name properties.
The XPath Selector defines and selects a fact. If a
vocabulary definition is referring to a fact rather than a slot, the
XPath Field property will be empty. However, there will be an additional
XPath expression in the XPath Field property if the vocabulary
definition is referring to a slot. This XPath expression is used to
select a descendant node of the fact. The engine will throw an exception
if a fact exists and it tries to evaluate a business rule condition
depending on this fact, but the vocabulary in the condition refers to a
slot that does not exist in the message instance asserted into the
engine's working memory.
If the fact does not exist, no error would occur in
the first place. Very simply, the engine would not be able to assert the
fact and would therefore realize that it cannot evaluate any rule
conditions that depend on this fact. If the fact exists, the engine
assumes that the child element exists and throws an error when it tries
to access the nonexistent element.
To ensure that you do not run into such situations,
you can edit the XPath Selector so that it only selects fact instances
with the required slots defined. XPath supports filters that you can use
to amend the XPath Selector to ensure those required slots exist.
For example, if you had a message like this one:
<MyMessage>
<Fields>
<Field1/>
<Field2> MyField2 value </Field2>
</Fields>
</MyMessage>
a vocabulary named MyDataField defined to reference Field2 will have an XPath Selector value of
/*[local-name()='My_Message' and namespace
uri()='http://schemas.test.com/20090307/MyMessageSchema']/*
[local-name()='MyMessage' and namespace
uri()='']/*[local-name()='Fields' and namespace-uri ()='']
and an XPath Field value of
*[local-name()='Field2' and namespace-uri()='']
To avoid exceptions if an asserted My_Message instance does not have a Field2 element defined, you can modify the XPath Selector to the following:
/*[local-name()='My_Message' and namespace-
uri()='http://schemas.test.com/20090307/MyMessageSchema']/*
[local-name()='MyMessage' and namespace-
uri()='']/*[local-name()='Fields' and namespace-uri()=''][Field2]
You can improve this filtering process further by modifying the XPath Selector to select My_Message nodes with a Field2 child element, which has a nonempty text node only:
/*[local-name()='My_Message' and namespace-
uri()='http://schemas.test.com/20090307/MyMessageSchema']/*
[local-name()='MyMessage' and namespace-
uri()='']/*[local-name()='Fields' and namespace-uri()=''][Field2!=""]
The key to effectively using the BRE and using XML
facts is to understand XPath and the difference between facts and slots,
and to edit your XPath Selectors and XPath Fields accordingly to meet
your needs. A good example is a business rule that should perform an
action only if a certain number of fields have the same value. For
instance, an institute that wants to automate the selection of courses
it offers to its students would use a business rule that looks at a
feedback summary report for a class and adds the class to the offered
courses roster only if ten students or more responded that the course
was "Very Good". This could be implemented through a set of complex
business rules or custom code. A better alternative is to leverage XPath
to define a vocabulary item that represents the count of "Very Good"
responses.
Assuming the feedback summary is as follows:
<CourseFeedback>
<Course title="Introduction to BizTalk 2009" />
<Instructor>John Smith</Instructor>
<WasThisCourseUseful>
<answer value="Good"/>
<answer value="Bad"/>
<answer value="Very Good"/>
<answer value="Good"/>
<answer value="Very Good"/>
<answer value="Very Good"/>
<answer value="Very Good"/>
<answer value="Very Good"/>
<answer value="Very Good"/>
<answer value="Very Good"/>
<answer value="Very Good"/>
<answer value="Very Good"/>
<answer value="Very Good"/>
</WasThisCourseUseful>
<WouldYouRecommendThisCourseToAFriend>
<answer value="Good"/>
<answer value="Bad"/>
<answer value="Very Good"/>
<answer value="Good"/>
<answer value="Very Good"/>
<answer value="Very Good"/>
<answer value="Very Good"/>
<answer value="Very Good"/>
<answer value="Very Good"/>
<answer value="Very Good"/>
<answer value="Very Good"/>
<answer value="Very Good"/>
<answer value="Very Good"/>
</WouldYouRecommendThisCourseToAFriend>
<CourseOnRoster>Yes</CourseOnRoster>
</CourseFeedback>
you could define a vocabulary item named GoodCount that counts the number of "Very Good" answers as follows:
XPath Selector: /*[local-name()='CourseFeedback' and namespace-
uri()='http://schemas.test.com/20090307"/
CourseFeedbackSchema']/*[local-name()='WasThisCourseUseful'
and namespace-uri()='']/*[local-
name()='WouldYouRecommendThisCourseToAFriend' and namespace-uri()='']
XPath Field: Count(//answer[@value="Very Good"])
You can then define a business rule as part of the
policy that checks whether the count is greater than ten, and if so sets
the course to be OnRoster.
XPath Selector: /*[local-name()='CourseFeedback' and namespace-uri()=
'http://schemas.test.com/20090307']
XPath Field: *[local-name()='CourseOnRoster' and namespace-uri()='']
SetCourseOnRoster (priority = 0)
IF GoodCount
is greater than or equal to 10
THEN CourseOnRoster = "Yes"
ELSE CourseOnRoster = "No"
Leveraging XPath queries in the definition of XPath
Field properties is a great way to minimize custom code and optimize the
execution of the BRE to evaluate complex rules.
4.1.2. Custom Fact retrievers
Fact retrievers are used to manage long-term facts
used by business policies. "If a fact changes infrequently, rule
processing efficiency can be obtained by saving it as a long-term fact
and loading it into memory to reuse. By referencing this fact retriever
in a policy, the user ensures that the engine (more accurately the
policy class) will call the fact retriever to get long-term facts" .
To expose long-term facts to the BRE and leverage
them in the definition of business rules and policies, you may use
custom fact retrievers, which are custom .NET classes that implement the
Microsoft.RuleEngine.IFactRetriever interface. This interface has a single public method, UpdateFacts.
A particular fact retriever may be associated with a particular policy
version through the policy property settings. This indicates to the BRE
that an instance of that fact retriever object should be instantiated
and the method UpdateFacts called to update all custom facts
associated with that particular policy. It is the responsibility of the
fact retriever to determine when the fact base has changed.
NOTE
A long-term fact only needs to be asserted once
for the same rule engine instance. For example, when you use the Call
Rules shape in an orchestration, the policy instance is moved into an
internal cache. At this time, all short-term facts are retracted and
long-term facts are kept. If the same policy is called again, either by
the same orchestration instance or by a different orchestration instance
in the same host, this policy instance is fetched from the cache and
reused. In some batch processing scenarios, several policy instances of
the same policy could be created. If a new policy instance is created,
you must ensure that the correct long-term facts are asserted.
The following custom fact retriever, DbFactRetriever,
selects a set of rows from a database table, adds them to a typed data
table, and asserts it as a fact. You can also add your own code to
determine whether cached data is obsolete and should be updated.
...
public class DbFactRetriever:IFactRetriever
{
public object UpdateFacts(RuleSetInfo rulesetInfo,
Microsoft.RuleEngine.RuleEngine engine, object factsHandleIn)
{
object factsHandleOut;
// The following logic asserts the required DB rows only once and always
// uses the same values (cached) during the first retrieval in
// subsequent execution cycles
if (factsHandleIn == null)
{
string strCmdSqlCon = "Persist Security Info=False;"+
"Integrated Security=SSPI;database=mydatabasename;server=myservername";
SqlConnection conSql = new SqlConnection(strCmdSqlCon);
// Using data connection binding
// DataConnection dcSqlCon = new DataConnection("Northwind", "CustInfo",
// conSql);
// Using data table binding
SqlDataAdapter dAdaptSql = new SqlDataAdapter();
dAdaptSql.TableMappings.Add("Table", "CustInfo");
conSql.Open();
SqlCommand myCommand = new SqlCommand("SELECT * FROM CustInfo", conSql);
myCommand.CommandType = CommandType.Text;
dAdaptSql.SelectCommand = myCommand;
DataSet ds = new DataSet("Northwind");
dAdaptSql.Fill(ds);
TypedDataTable tdtCustInfo = new TypedDataTable(ds.Tables["CustInfo"]);
engine.Assert(tdtCustInfo);
factsHandleOut = tdtCustInfo;
}
else
{
factsHandleOut = factsHandleIn;
}
return factsHandleOut;
}
}
...
4.2. Conditions
After creating the vocabulary and publishing it to
the rule store, the business rule creator can now create the business
rules constituting the business policies. The creation of the business
rules constitutes creating a set of conditions and actions for each
rule.
A condition is simply a Boolean expression that consists of one or more predicates applied to facts.
"Predicates can be combined with the logical connectives AND, OR, and
NOT to form a logical expression that can be potentially quite large,
but will always evaluate to either true or false" .
A set of predefined predicates are available in the Business Rules Framework:
After: Tests whether a date/time fact happens after another date/time fact
Before: Tests whether a date/time fact happens before another date/time fact
Between: Tests whether a date/time fact is in the range between two other date/time facts
Exists: Tests for the existence of an XML node within an XML document
Match: Tests whether the specified text fact contains a substring that matches a specified regular expression or another fact
Range: Tests whether a value is within a range defined by the lower-bound value (inclusive) and upper-bound value (inclusive)
Equal: The equality relational operator
GreaterThan: The greater than relational operator
GreaterThanEqual: The greater than or equal relational operator
LessThan: The less than relational operator
LessThanEqual: The less than or equal relational operator
NotEqual: The not equal to relational operator
4.3. Actions
Actions are the functional consequences of condition
evaluation. If a rule condition is met, a corresponding action or
multiple actions will be initiated. Actions can result in more rules
being evaluated and trigger a chain effect. They are represented in the
Business Rules Frame-work by invoking methods or setting properties on
objects, or by performing set operations on XML documents or database
tables. The Business Rules Framework provides a set of pre-defined
functions that can be used in actions:
NOTE
To assert a .NET object from within a rule, you can add the built-in Assert function as a rule action. The rule engine has a CreateObject
function, but it is not displayed explicitly with the rest of the
functions in the Facts Explorer window in the Business Rule Composer. By
simply dragging the constructor method of the object you wish to create
from the .NET Class view of the Facts Explorer to the action pane, the
Business Rule Composer will translate the constructor method into a CreateObject call in the rule definition (Moons, 2005).
Update: Refreshes the specified fact
in the current rule engine instance. If this fact is used in business
rule conditions in the current policy, this will result in those rules
being reevaluated. Rules that use the fact being updated in their
actions will not be reevaluated and their actions will remain on the
agenda.
A rule with an action that updates the value of a
fact being used in its condition evaluation might result in a cyclical
valuation loop, if the value used to update the fact always results in
the condition being evaluated to true. By default, the Business Rule
Engine will cycle through 2⁁32 loops before it exits the match–conflict
resolution–action cycle . This value is a configurable property per policy version.
|
|
Retract: Removes the specified fact from the current rule engine instance.
RetractByType: Removes all existing facts of the specified fact type from the current rule engine instance.
Clear: Clears all facts and rule firings from the current rule engine instance.
Halt:
Halts the current rule engine execution and optionally clears all rule
firings. The facts remain unaffected so that values are returned.
Executor: Returns a reference to the current rule engine instance of type IRuleSetExecutor.
FindAll: Returns a string containing all substrings that match a specified regular expression in the specified text.
FindFirst: Returns a string containing the first substring that matches a specified regular expression in the specified text.
Add: Adds two numeric values.
Subtract: Subtracts two numeric values.
Multiply: Multiplies two numeric values.
Divide: Divides two numeric values.
Power: Returns the result of a number raised to a power.
Remainder: Returns the remainder after a number is divided by a divisor.
Year: Returns the year component of the specified date/time fact, a value in the range 1 to 9999.
Month: Returns the month component from the specified date/time fact, a number from 1 to 12.
Day: Returns the day of the month component from the specified date/time fact, a number from 1 to 31.
Hour: Returns the hour component from the specified date/time fact, a number from 0 (12:00 a.m.) to 23 (11:00 p.m.).
Minute: Returns the minute component from the specified date/time fact, a number from 0 to 59.
Second: Returns the second component from the specified date/time fact, a number from 0 to 59.
TimeOfDay: Returns the time component from the specified date/time fact.
DayOfWeek: Returns the day of the week from the specified date/time fact, a number from 0 (Sunday) to 6 (Saturday).
8.4.4. Rules and Priorities
The BRE implements the RETE algorithm.
By default the execution of rule actions is nondeterministic. The
engine evaluates all rules in the policy and creates an agenda of
actions for rules with valid conditions to be executed. The execution of
actions on the agenda might result in condition reevaluation or more
conditions being evaluated, if those actions update or assert new facts.
With all rules having the same priority, there is no guaranteed order
of execution for the actions on the agenda. To guarantee an order of
execution, you need to resort to using rule priorities.
The default priority for all rules is zero. Priority
for execution is set on each individual rule. The priority is a positive
or negative integer value, with larger numbers having higher priority.
Actions added to the agenda for rules with valid conditions are executed
in order from highest priority to lowest priority.
To see how priority affects the execution order of
rules, take a look at the following example from the BizTalk Server 2009
documentation (Microsoft, 2009):
Rule1 (priority = 0)
IF Fact1 == 1
THEN Discount = 10%
Rule2 (priority = 10)
IF Fact1 > 0
THEN Discount = 15%
Although the conditions for both rules have been met,
Rule2, having the higher priority, is executed first. The action for
Rule1 is executed last, and so the final discount is 10%, as
demonstrated here:
Working Memory | Agenda |
---|
Fact1 (value=1) | Rule2 Discount = 15% |
| Rule1 Discount = 10% |