Export (0) Print
Expand All

Risk Scoring with BizTalk Server 2004 and the Business Rules Framework

Microsoft Corporation

March 2005

Applies to: BizTalk Server 2004

Summary: "Risk Scoring with BizTalk Server 2004 and the Business Rules Framework" presents an example BizTalk solution in which an insurance company uses the Business Rules Framework to create a risk profile for qualifying applicants. (21 printed pages)

Download sample code here

The Business Rules Framework, supplied with BizTalk Server 2004, is a technology that enables you to build, test, and deploy flexible rules that support your business processes. This article takes a hands-on, "how-to" approach and gives you some background regarding the Business Rules Framework before walking through the component parts of an example solution. The business scenario presented here is just an example – the technology and principles discussed throughout this article are applicable to a very wide range of applications and scenarios.

This article assumes that the reader is reasonably familiar with BizTalk Server 2004 and Visual C#. Code samples in this article are presented in C# but the code that accompanies the article includes both C# and Microsoft® Visual Basic®. No familiarity with the Business Rules Framework is assumed. This article explores an example risk-scoring process that we need to integrate into a larger BizTalk Server application. A fictional insurance company uses this risk-scoring process to create a view of each potential insurance applicant. This view is called a risk profile and it categorizes and scores different types of risk based on what is known about the applicant. The insurance company could then use this risk profile to calculate how much to charge for an insurance policy.

Our BizTalk Server application will provide an input XML document that contains information about an applicant that they provided when they first applied for insurance. We need to apply a set of business rules to this document to identify a set of risk factors that we can then use to build our risk profile. A risk factor identifies a category of risk with a score against it to indicate how significant that particular risk appears to be. For example, we might have a risk factor for health with a score that indicates the extent to which the applicant's health appears to be a risk. Our output risk profile is built as an XML document that will subsequently be processed by the rest of our BizTalk Server application.

Flexibility is an important requirement for our system. Business rules change frequently, so we need a way to rapidly put new or modified rules into production. We also need to enable our business analysts to work directly with our business rules – we don't want to tie up developers for a long time just to change a parameter value for one of our rules.

A key point to recognize at this early stage is the need to work with two different XML documents - input details about the applicant and an output risk profile - and the need to dynamically modify the contents of the risk profile document as we apply our business rules. We need to do something more than simply assigning values to pre-existing elements in an XML document. Dynamically modifying an XML document usually requires knowledge about things like XPath and DOM objects and so is typically in the developer's domain. We need to enable business analysts to do this by encapsulating the XML manipulation functionality that we need inside something that lets them focus on the business process.

The Business Rules Framework is a technology supplied with BizTalk Server 2004 aimed at the kind of scenario described above. It is made up of a number of .NET components, tools, and wizards that provide design-time support for building business rules as well as a runtime environment for executing your rules. After you have defined and tested a set of rules, it is easy to integrate them into a BizTalk orchestration or even call them directly from your own .NET applications.

Before digging into the technology, I want to say a little about the motivations behind the Business Rules Framework. Every business process needs rules. When you' apply for a loan, pay for groceries at your local supermarket, or check in at an airport, you are part of a business process that uses specific criteria to decide an appropriate outcome. Traditionally, these rules have been developed as procedural code in languages like Microsoft® Visual Basic®. Unfortunately, this approach can have some significant downsides.

Developing and testing this kind of procedural code requires developers to translate the requirements identified by business analysts into a form that their chosen language can understand. Coding complex, interrelated rules can be a tricky business and once your rules are encapsulated in code they can be hard for non-developers to understand. After it is deployed, it is very likely that your business rules will need to change over time. The effort involved in modifying, re-testing and then redeploying your business rules can be significant and doesn't fit well with today's vision of the agile business.

The Business Rules Framework takes a very different approach. The business rules that it executes are defined declaratively without a need for procedural instructions. As a result, the rules that you define are much closer to the way that they were originally envisioned by a business analyst. To make rule development accessible to more people in your organization, a straightforward graphical user interface is provided for rule creation and management. This tool allows you to focus on what your rules need to achieve rather than dealing with complex implementation detail. At runtime, a sophisticated forward-chaining inference engine processes your rules and takes the actions that you specified.

At this point we need to introduce some terminology and fundamental concepts. The Business Rules Framework is based on facts, vocabulary, definitions, and policy. Facts are the entities that your rules consume and manipulate – examples include an element that appears in an XML document, a column in a database table, or even a method exposed by a .NET component. A vocabulary is a collection of facts, and its purpose is both to apply meaningful, user-defined definition names to those facts and to provide a unit of versioning. Vocabulary definitions provide a neat level of abstraction that keeps procedural detail (for example, the SQL statement needed to provide a value from a database) separate from the definition names you refer to when writing your rules. You can use the same vocabulary across many different rules and rule sets.

We've referred to 'rules' many times so far without really defining what they are. In terms of the Business Rules Framework, a rule is a statement that defines the behavior of one aspect of a business process. For example, you might have a rule that says "If the loan applicant has a credit rating of 'good' then immediately approve the loan." This example rule shows us that rules are made up of facts, conditions and actions.

Fact: loan applicant's credit rating

Condition: credit rating is equal to 'good'

Action: immediately approve the loan

Conditions always evaluate either to true or false based on one or more predicates being applied to facts. In this case, the predicate is "is equal to". You can combine predicates with the logical conjunctions AND, OR, and NOT to build up complex conditions. Actions define what happens when your condition evaluates to true.

The last term that we need to define is policy. A policy is a set of rules, packaged and versioned as a unit. Policies are what you develop, test, and deploy when working with the Business Rules Framework. When you publish a version of your policy it can not be changed. This makes it easy to manage well-defined versions that contain consistent, predictable behavior. One final - and really important - thing that about policy is that you can update a policy in your production environment without needing to recompile and redeploy the BizTalk orchestrations that make use of it. Your BizTalk Server application will start using the new version in near real-time without any downtime.

Here's a brief rundown of the software pieces that make up the Business Rules Framework:

Business Rules Composer: This application enables you to define vocabularies and build and test rules. The rule-building experience is as simple as dragging and dropping facts and setting properties. You can also publish and deploy policies from here.

Rules Engine Deployment Wizard: Vocabularies and policies are stored in a SQL Server rule store database. To help you move a vocabulary or policy to another computer, this wizard provides import/export functionality. You can also deploy or undeploy a policy.

Rules Engine: This is the runtime engine that processes your policies. The engine evaluates rules, based on their facts, and decides which actions need to be executed. It also supports complexities like dealing with forward chaining. Forward chaining refers to the situation where your rule conditions modify the facts used in your policy; as the underlying facts change, the engine determines when it needs to re-evaluate your rule conditions.

The rules engine is implemented as a standalone .NET component. It can be called from inside BizTalk Serveror loaded into a process of your choice via a comprehensive API. With this is mind, you can install the rules engine on its own using the BizTalk Server 2004 installer or as part of a larger BizTalk Server installation.

This paper does not go into too much more technical detail about the Business Rules Framework because the SDK documentation does a great job of providing that. See http://go.microsoft.com/fwlink/?LinkId=42042 for more detail.

Building Our Vocabulary

The first thing we need to do is to identify the facts involved in our business scenario. We have four key facts – our input XML document that contains applicant details, our output XML document that contains our applicant's risk profile, and two .NET components. Let's look at each of these in turn.

This scenario presents an entirely fictional process for a fictional company and works on data relating to fictional people. The data and business rules are kept simple – the important thing that we're examining here is the way that the tools and technologies can be used, not the intricacies of our fictional insurance company's business processes, or the XML schemas that they could involve.

Input XML Document

This document represents pertinent details about our applicant. The schema is very simple and contains some of the details that our fictional company could have captured on an application form. If you're not familiar with it, the <annotation> element that appears in the schema is being used here to distinguish one of the elements specified in the schema. Distinguishing an element makes it simpler to work with in expressions that appear in a BizTalk orchestration.


<?xml version="1.0" encoding="utf-16"?>
<xs:schema xmlns="http://RiskScoring" xmlns:b="http://schemas.microsoft.com/BizTalk/2003" elementFormDefault="qualified" targetNamespace="http://RiskScoring" xmlns:xs="http://www.w3.org/2001/XMLSchema">
  <xs:element name="ApplicantDetails">
    <xs:annotation>
      <xs:appinfo>
        <b:properties>
          <b:property distinguished="true" xpath="/*[local-name()='ApplicantDetails' and namespace-uri()='http://RiskScoring']/*[local-name()='ApplicationID' and namespace-uri()='http://RiskScoring']" />
        </b:properties>
      </xs:appinfo>
    </xs:annotation>
    <xs:complexType>
      <xs:sequence>
        <xs:element name="ApplicationID" type="xs:long" />
        <xs:element minOccurs="1" maxOccurs="1" name="PersonalDetails">
          <xs:complexType>
            <xs:sequence>
              <xs:element name="Name" type="xs:string" />
              <xs:element name="Age" type="xs:int" />
              <xs:element name="CurrentHealth" type="HealthStatus" />
            </xs:sequence>
          </xs:complexType>
        </xs:element>
        <xs:element minOccurs="1" maxOccurs="1" name="Interests">
          <xs:complexType>
            <xs:sequence>
              <xs:element name="LionTaming" type="xs:boolean" />
              <xs:element name="Reading" type="xs:boolean" />
              <xs:element name="ExtremeSports" type="xs:boolean" />
              <xs:element name="WatchingTV" type="xs:boolean" />
              <xs:element name="BearWrestling" type="xs:boolean" />
            </xs:sequence>
          </xs:complexType>
        </xs:element>
        <xs:element minOccurs="1" maxOccurs="1" name="FinancialDetails">
          <xs:complexType>
            <xs:sequence>
              <xs:element name="NumTimesBankrupt" type="xs:int" />
              <xs:element name="AnnualIncome" type="xs:int" />
              <xs:element name="AnnualExpenditure" type="xs:int" />
              <xs:element name="OwnProperty" type="xs:boolean" />
            </xs:sequence>
          </xs:complexType>
        </xs:element>
      </xs:sequence>
    </xs:complexType>
  </xs:element>
  <xs:simpleType name="HealthStatus">
    <xs:restriction base="xs:string">
      <xs:enumeration value="Great" />
      <xs:enumeration value="OK" />
      <xs:enumeration value="Poor" />
    </xs:restriction>
  </xs:simpleType>
</xs:schema>

Below is an example instance document, belonging to a fairly risky-looking applicant, which conforms to this schema:


<ns0:ApplicantDetails xmlns:ns0="http://RiskScoring">
  <ns0:ApplicationID>10</ns0:ApplicationID>
  <ns0:PersonalDetails>
    <ns0:Name>John Dangerlover</ns0:Name>
    <ns0:Age>99</ns0:Age>
    <ns0:CurrentHealth>Poor</ns0:CurrentHealth>
  </ns0:PersonalDetails>
  <ns0:Interests>
    <ns0:LionTaming>true</ns0:LionTaming>
    <ns0:Reading>true</ns0:Reading>
    <ns0:ExtremeSports>true</ns0:ExtremeSports>
    <ns0:WatchingTV>true</ns0:WatchingTV>
    <ns0:BearWrestling>true</ns0:BearWrestling>
  </ns0:Interests>
  <ns0:FinancialDetails>
    <ns0:NumTimesBankrupt>10</ns0:NumTimesBankrupt>
    <ns0:AnnualIncome>1000</ns0:AnnualIncome>
    <ns0:AnnualExpenditure>10000</ns0:AnnualExpenditure>
    <ns0:OwnProperty>false</ns0:OwnProperty>
  </ns0:FinancialDetails>
</ns0:ApplicantDetails>

Output XML document

This document represents the output from our risk scoring rules. This document is a risk profile, detailing risk by category – our application can then use this risk profile to decide how much we should charge for insurance (or maybe whether we should insure the applicant at all!). We capture information about perceived risk organized by category (personal information, financial details and applicant's interests) with subtotals at each category level and a grand total that adds each subtotal together.

An important thing to note about our schema is that the score for each risk category needs to be built up incrementally as our rules get processed. Each rule adds or subtracts a value to the category that it relates to and adds an element into the relevant <Details> section to provide both an audit trail and commentary on the score adjustment. As rules execute, the risk scores in each category (and the corresponding totals) move up and down. As you can see, the risk profile needs to be constructed in a very dynamic manner. Finally, note that in our scenario a higher score in any risk profile category indicates higher risk.

Here's the schema for our risk profile:


<?xml version="1.0" encoding="utf-16"?>
<xs:schema xmlns="http://RiskScoring" xmlns:b="http://schemas.microsoft.com/BizTalk/2003" elementFormDefault="qualified" targetNamespace="http://RiskScoring" xmlns:xs="http://www.w3.org/2001/XMLSchema">
  <xs:element name="RiskProfile">
    <xs:complexType>
      <xs:sequence>
        <xs:element name="ApplicationID" type="xs:long" />
        <xs:element name="OverallRisk" type="xs:int" />
        <xs:element name="DateCreated" type="xs:dateTime" />
        <xs:element name="RiskElements">
          <xs:complexType>
            <xs:sequence>
              <xs:element name="Personal" type="RiskElement"/>
              <xs:element name="Financial" type="RiskElement" />
              <xs:element name="Interests" type="RiskElement" />
            </xs:sequence>
          </xs:complexType>
        </xs:element>
      </xs:sequence>
    </xs:complexType>
  </xs:element>
  <xs:complexType name="RiskElement">
    <xs:sequence>
      <xs:element name="Details">
        <xs:complexType>
          <xs:sequence minOccurs="0" maxOccurs="10">
            <xs:element name="Detail" type="xs:string" />
          </xs:sequence>
        </xs:complexType>
      </xs:element>
    </xs:sequence>
    <xs:attribute name="Score" type="xs:int" />
  </xs:complexType>
</xs:schema>

And here's an example risk profile instance document:


<?xml version="1.0" encoding="utf-16"?>
<RiskProfile xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://RiskScoring">
<ApplicationID>10</ApplicationID>
<OverallRisk>1000</OverallRisk>
<DateCreated>2004-11-26T16:22:01.2775888-08:00</DateCreated>
<RiskElements>
<Personal Score="0">
<Details />
</Personal>
<Financial Score="1000"> 
<Details>
<Detail>Applicant failed credit check</Detail>
</Details>
</Financial>
<Interests Score="0">
<Details />
</Interests>
</RiskElements>
</RiskProfile>

The final two facts that we need are two .NET components. Being able to incorporate .NET components into your rules as facts is an important feature that significantly increases the rules' flexibility. Rather than just working on static data, this feature allows your rules to interact with instances of your components – reading and writing properties, calling methods – to provide a programmable and dynamic element to your business rules. Our rules make use of two .NET components.

RiskProfileHelper Component

This component helps us build our output risk profile document. Why do we need a .NET component here at all? Well, if you've already played with the Business Rules Composer you will have seen that it makes it very easy for us to create rules that assign values to elements in a pre-existing XML document. However, our scenario is more complex than that. We need to dynamically alter the score of our risk elements as our rules are invoked, and also dynamically add new detail elements to our risk profile. Our component will do this for us, allowing our business analysts to define rules that perform this dynamic work without exposing them to any of the complexities involved in XML manipulation. Our component will also provide some other functionality like creating an empty risk profile document for our rules to work on and exposing the risk profile document to calling code. Let's take a quick look at the key members of this component in a little more detail.

Public RiskProfile RiskProfileDocument

This is a public property that exposes the risk profile document that we're working on. The type of this property is RiskProfile – this is a type generated for us by running xsd.exe against our risk profile schema. If you've never used it before, xsd.exe is a great way to leverage the .NET XML serializer to provide classes (or DataSets) that you can code against rather than having to directly manipulate XML. These classes are already decorated with everything they need to neatly serialize back into XML of the correct structure. Most developers find working with a class much more straightforward than working with XML, especially when you factor in Microsoft® IntellliSense® support in Visual Studio.

public void AddRisk(RiskCategory category, int scoreAdjustment, string detail)

The purpose of this method is to update our risk profile by adjusting the score of the relevant category by the specified value and also adding a new detail element to the relevant section.

private void SaveRiskProfileToFile(string filePath)

This method does just what you would expect – it serializes the risk profile out to a file. The ability to see the risk profile document will be very useful when we test our scoring rules.

A couple of other points of interest about this class:

  • The class includes a finalizer. It is not a good idea to include a finalizer unless you really need one, but this is included as a debug-only way of ensuring that the class always saves the risk profile out to a file for inspection. This option means that we don't need to pollute our policy with a special low-priority rule to call this method. When the code is built in release mode, the finalizer disappears.
  • The class includes a private ArrayList for each set of RiskElements/*/Details/Detail elements. When the document is requested using the RiskProfileDocument property, these ArrayLists are used to populate the corresponding arrays in the returned RiskProfile object. I did this because ArrayLists are a better fit for this situation – we know we will need to dynamically re-dimension our set of detail elements every time we call AddRisk(). Doing this against a regular Array would introduce an unnecessary overhead. An ArrayList allows us to grow our set of elements as required and only manipulate the regular arrays on our RiskProfile object when we need to.

CreditCheck Component

The final piece of our vocabulary is this second .NET component. This simple component represents some credit-scoring functionality that our rules need to call on. In this example the logic is some very simple local code – in a real system this component would call out to a real credit-scoring service, perhaps via a web service. The class has two key members:

public Status CurrentStatus

This is a read-only property that exposes a credit score status. The value of the status is selected from an enumeration exposed by the class. The status is initially set to NotChecked. Following credit scoring, this status is set to either CheckedOK or CheckedReject.

public void CheckCredit(int annualIncome, int annualExpenditure, int numberTimesBankrupt)

This method uses some simple credit-scoring logic to update the current status.

For convenience, both the RiskProfileHelper and CreditCheck classes are in the same project. This C# version of this project has a post-build event to install the output assembly into the Global Assembly Cache. We need our assembly to be installed here so that we can incorporate it into our vocabulary. If you are using the Visual Basic .NET version of the code, a batch file is provided to install the assembly into the GAC. Note that you may need to tweak the path used by either the post-build event or the batch file to point to the correct location of gacutil.exe on your development machine.

Putting the Vocabulary Together

To actually build our vocabulary we'll be using the Business Rules Composer. The user interface to do this is very straightforward. Here are the basic steps:

  1. Open up the Business Rules Composer.
  2. Connect to your local BizTalk Rules Engine database (the default name is BizTalkRuleEngineDb).
  3. Select the Vocabularies tab in the Facts Explorer window.
  4. Right-click on the Vocabularies folder and select Add New Vocabulary.
  5. Give the vocabulary the name RiskScoring

Alternatively, the code for this article includes a completed vocabulary file – if you don't want to build the vocabulary for yourself, feel free to import the completed vocabulary via the Rules Engine Deployment Wizard. There is more information about using this tool later in this paper.

It is important to know that facts don't actually need to belong to a vocabulary. You are free to add facts via the Facts Explorer window and use them in a rule without adding them to a vocabulary first. On the other hand, a published vocabulary provides you with an immutable and reusable set of constraints and facts with user-friendly names. It is usually worth the effort involved to create a vocabulary, we will do that for our scenario in this paper.

We now have a vocabulary to hold our facts. Our facts will be a combination of elements from our applicant details schema as well as members from our two .NET components. These instructions assume that you are using the schema files included with this article's code and that you have already installed the RiskScoringHelper assembly into your local Global Assembly Cache. You can do this either by building the assembly - if you're using the C# code - or by running the supplied batch file corresponding to either the C# or VB.NET version of the code.

First, let's add the facts drawn from applicant details schema elements. By adding each element to our vocabulary we can provide an aliased name that will allow our users to focus on using the data in their business rules rather than dealing with any XPath:

  1. Right-click on your vocabulary and select Add New Definition.
    The Vocabulary Definition Wizard appears.
  2. Select XML Document Element or Attribute and then click Next.
  3. Click the Browse button and navigate to ApplicantDetails.xsd.
  4. Expand the root ApplicantDetails element and then expand the child PersonalDetails element. Next, select the Age element and then click OK
  5. Update the Definition Name to Applicant's age.
  6. Update the Document Type to RiskScoringSchemas.ApplicantDetails.
    This ensures that the schema has the same type as the corresponding message in the BizTalk Server application that we will see later in the paper.
  7. Select the Perform "Get" operation radio button. We will be reading only this value.
  8. Ensure that the Display Name is the same as the Definition Name.
  9. Click Finish.

We need to repeat the steps above for the remaining schema elements that we will be referencing in our rules. The following table shows the complete set of elements that need to be added to our vocabulary.

Table 1 Vocabulary elements

Fact name Schema element

Applicant's age

/PersonalDetails/Age

Applicant's current health status

/PersonalDetails/CurrentHealth

Applicant's interests include reading

/Interests/Reading

Applicant's interests include lion taming

/Interests/LionTaming

Applicant's interests include extreme sports

/Interests/ExtremeSports

Applicant's interests include wrestling bears

/Interests/BearWrestling

Applicant's annual income

/FinancialDetails/AnnualIncome

Applicant's annual expenditure

/FinancialDetails/AnnualExpenditure

The number of times that the applicant has been declared bankrupt

/FinancialDetails/NumTimesBankrupt

The second type of fact that we need to add to our vocabulary are the members from our .NET components that we will be using in our rules.

  1. Right-click your vocabulary and select Add New Definition.
  2. The Vocabulary Definition Wizard appears. This time, select .NET Class or Class and then click Next.
  3. Click Browse and then scroll down the list of assemblies. Select RiskScoring.RiskScoringHelper and click OK.
  4. A dialog box now prompts us for the class or member that we need to expose as a fact. Select the RiskProfileHelper.AddRisk() method and click OK.
  5. Update the Definition Name to Update risk profile and then click Next.
  6. This wizard step lets us define how parameters are handled. Leave the Step 1 section as it is. In the Step 2 section we can control how the fact appears when it's added to a rule. Update the string here from the default to a more user-friendly "Update risk profile using category = {0} score adjustment = {1} risk detail = {2}".
  7. Click Finish.

We need to repeat steps 1 to 7 for the following .NET component members

  • CreditCheck.CurrentStatus property (definition name = "Credit check status", no need to specify display format string). Note that this property appears as "get_CurrentStatus" in the binding selection dialog.
  • CreditCheck.CheckCredit() method (definition name = "Run credit check", display format string = "Run credit check using income = {0} expenditure = {1} number of times bankrupt = {2}")

The last item that we need to add is a reference to an instance of our CreditCheck component. We will need this fact later on to provide a parameter to a rule engine control function in one of our more complex rules. Repeat steps 1 to 7, adding the CreditCheck class itself as a fact with a definition name of "CreditCheck".

Finally, we need to save and publish our new vocabulary. Right-click on your vocabulary and select Save. Then right-click it again and select Publish.

We now have everything we need to start building our rules.

As discussed earlier, rules are grouped into policies. Our policy will contain six rules that we want to run against every set of applicant details to produce a risk profile. Over time, we expect these rules to be modified – not just tweaks to parameters, but also new rules added and existing rules removed. The Business Rules Composer makes this easy to achieve without becoming involved in software development.

The code for this article includes a completed policy file – if you don't want to build the rules for yourself, you can import the policy using the Rules Engine Deployment Wizard.

Before we start adding rules, we need a policy to contain them.

To create a new policy:

  1. Open the Business Rules Composer.
  2. Connect to your local BizTalk rules engine database (default name is BizTalkRuleEngineDb).
  3. Select the Policies folder in the Policy Explorer Window.
  4. Right-click on the Policies folder and select Add New Policy.
  5. Rename the new policy RiskScoring.

This creates version 1.0 of your policy. The first three rules that we need to build are detailed below. The rules appear here as they appear in the Business Rules Composer.

Rule: AgedOver70

IF

Applicant's age is greater than 70

THEN

Update risk profile using category = Personal score adjustment = 10 risk detail = Applicant is aged over 70

Rule: PoorHealth

IF

Applicant's current health status is equal to Poor

THEN

Update risk profile using category = Personal score adjustment = 10 risk detail = Applicant is in poor health

Name: ReadingReward

IF

Applicant's interests include reading is equal to true

THEN

Update risk profile using category = Interests score adjustment = -10 risk detail = Applicant is a reader

These rules are pretty straightforward. Our risk scoring process requires a number of basic rules that check values in the input document and then add or subtract risk values based on what we find. The first two rules add some risk if the applicant is aged over 70 or in poor health. The final rule rewards the applicant for being a reader by reducing the perceived risk. As you can see, these rules are very declarative and very different from writing regular procedural code.

An interesting point regarding all of our rules is that although they are completely independent from each other they also work together with our RiskProfileHelper to build up an overall picture of risk. For example, we don't need to add a new rule that looks for applicants over 70 who are also in poor health because in that circumstance both of our two more granular rules (AgedOver70 and PoorHealth) will increment the overall risk score. Of course, if we wanted to do something different in that circumstance we could just add a new combined rule.

For each of the rules above, we check the value of an element in the input document and if it meets our specified criteria we call our "Update risk profile" fact. The procedure to build each of these rules is essentially the same (apart from the specific data elements and parameter values involved, of course) so only the building of the first rule is described in detail.

  1. Right-click the RiskScoring Version 1.0 policy folder and select Add New Rule.
  2. Rename the rule to AgedOver70.
  3. Right-click the Conditions area on the right-hand pane and select Predicates | GreaterThan.
    We now need to populate each side of the predicate.
  4. Select the Vocabulary tab in the Facts Explorer window
  5. Drag the Applicant's age fact across onto the argument1 area of our rule.
  6. Click on the argument2 area and type 70.

We now need to add an action.

  1. Drag the Update risk profile fact across to the Actions area of our rule.
  2. Fill in the parameters with the required values: category = Personal, score adjustment = 10, risk detail = Applicant is aged over 70.
  3. Save the changes to your policy by clicking the Save All toolbar icon or by right-clicking the policy and selecting Save.

The next 3 rules are more complex and highlight a couple of additional rules engine features.

Rule: TooManyDangerousInterests

IF

Applicant's interests include lion taming is equal to true

AND

Applicant's interests include extreme sports is equal to true

AND

Applicant's interests include wrestling bears is equal to true

THEN

Update risk profile using category = Interests score adjustment = 100 risk detail = Too many dangerous interests

Rule: RequireCreditCheck

IF

The number of times that the applicant has been declared bankrupt is greater than 0

THEN

Run credit check using income = Applicant's annual income expenditure = Applicant's annual expenditure number of times bankrupt = The number of times the applicant has been declared bankrupt

update CreditCheck

Rule: FailedCreditCheck

IF

Credit check status is equal to CheckedReject

THEN

Update risk profile using category = Financial score adjustment = 100 risk detail = Applicant failed credit check

halt and clear all rule firings

This paper does not go into step-by-step detail on how to build these rules – the supplied code contains the full policy which you can import using the Business Rules Deployment Wizard. If you decide to build the rules for yourself, here are a few tips that you may find helpful:

  • If a rule has multiple actions, you can change their order by right-clicking an action and then selecting Move up or Move down. You may be interested to know that action ordering isn't actually significant for this policy. This is because engine control functions always execute last no matter where they are placed in the action block. If we had rules with several of our own actions then correct ordering could be important.
  • When you are setting up the right-hand side condition for the FailedCreditCheck rule, you may find that the Business Rules Composer does not initially provide you with the values from the Status enumeration. If this happens, set the value to 2 (the correct underlying value) and then close and re-open the Business Rules Composer. The value should now have changed from 2 to CheckedReject.

The TooManyDangerousInterests rule demonstrates the use of logical AND operators to build up a more complex rule. Our fictional company is happy for applicants to have one or two dangerous ways of spending their leisure time, but involvement in all three is just too many. The Business Rule Composer makes it easy to graphically construct this logic. If you right-click on the Conditions area of your rule there are options to add logical AND, OR and NOT operators to your rule condition. You can then add the predicate statements we saw earlier - equal to, greater than, and so on – by right-clicking on the new logical operator to create the required condition.

The last two rules are interrelated. RequireCreditCheck is the only rule we have that doesn't use the "Update risk profile" fact. The aim of this rule is to perform a credit check if we detect that the applicant has a value greater than zero in their NumTimesBankrupt element. To perform a credit check we need to call the CheckCredit() method exposed by our CreditCheck component. This call applies some simple logic and updates the current credit check status exposed by the component. Access to the CheckCredit() method in our vocabulary is through the "Run credit check" fact.

The CreditCheck component is also involved in the last rule. FailedCreditCheck looks at the CurrentStatus property of our CreditCheck instance – using the "Credit check status" fact - to find out whether or not the applicant has failed a credit check; if so, a large risk value is added to our risk profile. In fact, our fictional company takes a failed credit check so seriously that we want to stop processing rules at this point. This could be because, regardless of the overall risk score, the business process that handles the risk profile document will handle an application differently if the applicant fails a credit check. In this situation there's no need to continue processing our business rules.

Business Rules Framework Features

The rules described above make use of two interesting rules engine features. The simplest of these is the ability to halt rules processing. This is achieved by adding "halt and clear" as a second action to the FailedCreditCheck rule. Rules can have multiple actions, just as they can have multiple evaluation conditions. Halt can work in two different ways – you can either halt and leave the agenda as it is, or halt and also clear out the agenda. In our scenario the net result would be the same with either option. However, if you were running the rules engine directly from your own code you could see a difference in behavior. The second interesting feature is making use of the Update action to influence the rules that get executed. At this point you're probably wondering what the "agenda" refers to and exactly what the Update action is for. To explain this, let's take a brief look at how the rules engine actually processes your rules.

The rules engine executes your rules by moving through three stages – match, conflict resolution and then action. Matching involves looking for rules that could be invoked based on their conditions. The engine is smart enough to calculate conditions that appear in several rules only once and sometimes stores partial matches to speed up future matching work. At the end of the matching phase, all of the actions that could be invoked based on the current set of available facts are added to a list called the agenda.

Conflict resolution is about determining the order in which actions on the agenda should be invoked. You can influence the ordering here by assigning priority values to your rules. All rules have a default priority of zero but you can increase or decrease this as required. The ordering of your actions is significant because the execution of an action can influence the subsequent execution of the rules engine. For example, this is how the credit check rules in our example work. When RequireCreditCheck is invoked, it may or may not result in a change to the state of our CreditCheck component that, in turn, invokes the FailedCreditCheck rule. This feature is called forward chaining. The powerful thing about this is that we don't need to explicitly tell the engine to run one rule before another – this happens automatically based on the facts available during the matching phase.

In our scenario we want to avoid processing any further rules if we find that the applicant requires and subsequently fails a credit check. To achieve this we can set the priority of the RequireCreditCheck rule to 100 to ensure that it gets processed first if the applicant data meets the specified conditions. If this rule triggers a credit check that fails, we want to ensure that the FailedCreditCheck rules are invoked next. Again, we can increase the priority of this rule to 50 ensure that this happens. The absolute priority values used here are not significant; what's important is that they are both higher than the default of 0 and that RequireCreditCheck has a higher value than FailedCreditCheck.

The final phase is action. This involves executing the actions that belong to the selected rule. Some actions don't modify the state that the rules engine is working on. An example here would be the calls to AddRisk() via our "Update risk profile" fact. Calling this method doesn't influence the rule that we need to call next. On the other hand, as we just saw, the CheckCredit() method can influence the state of the rules engine. In this case, we need to tell the rules engine that something could have changed about the underlying CreditCheck instance, and that it needs to re-examine it to see if any other rules should now be invoked. We do this by adding an Update action to the RequireCreditCheck rule, passing the CreditCheck fact as a parameter.

The match-conflict resolution-action pattern is repeated until there are no more rules that should be invoked. To dig deeper into this topic, see the SDK documentation for more detail on how the engine executes your rules and the engine control actions that let you influence this.

At the moment we have a policy containing six rules that work with facts provided by our vocabulary. What we haven't seen so far is any rule execution. Testing your rules in the Business Rules Composer is straightforward but before we can begin there is one additional piece of code that we need to briefly explore – a fact creator.

At runtime our rules require three different facts to be available - an XML instance document containing applicant details, an instance of our CreditCheck component and an instance of our RiskProfileHelper component. Where do these facts come from? The short answer is that something needs to create them and then supply them to the rules engine. If we're running our rules from within a BizTalk orchestration then we can pass in orchestration variables of the relevant types. Things are slightly different when we're testing our rules inside the Business Rules Composer. The following figure shows the dialog that appears when you right-click on your policy and select Test Policy.

Figure 1 Select Facts dialog box

Select Facts screen

As you can see, the dialog box allows us to specify an XML file for our ApplicantDetails fact. To supply our .NET components we need to specify a fact creator. As the name suggests, this is a component that will supply instances of the required fact types. Implementing a fact creator is easy enough to do – the requirement is a component that implements the IFactCreator interface. Here's the code for our fact creator:


public class FactCreator : IFactCreator
{
public FactCreator()
{
}
#region IFactCreator Members

public object[] CreateFacts(RuleSetInfo ruleSetInfo)

{
CreditCheck cc = new CreditCheck();
object[] facts = new object[2]{
new RiskProfileHelper(),cc};
return facts;
}

public Type[] GetFactTypes(RuleSetInfo ruleSetInfo)
{
return null;
}

#endregion
} 

As you can see, IFactCreator requires us to implement two methods – CreateFacts supplies an array containing our fact instances and GetFactTypes supplies an array containing corresponding type information. Incidentally, even if you're only calling static members (Shared in Visual Basic) on a .NET class you always need to provide an instance of that class to the rules engine.

After you have an input XML document and have configured your fact creator, testing is as simple as clicking the Test button. As the rules engine processes your rules, details about the execution process are written out to the Output window. The output here shows you events including

  • Facts being added into memory for processing (Assert/Update/Retract operations)
  • Rule conditions getting evaluated
  • Agenda modifications
  • Rules that are invoked

In addition, when in debug mode, the RiskProfileHelper will save out the risk profile to a file in its finalizer. By default this file is placed in the root of your C: drive. This file gives you a simple way of checking the resulting risk profile alongside the rule execution output that appears in the Business Rules Composer.

When testing your policy, you can selectively enable and disable each rule by modifying its Active property. As discussed earlier, you can also tweak the Priority property if you need to influence the order in which your rules get processed.

When you've finished your testing, you need to publish and deploy your policy. You can perform each of these tasks by right-clicking on your policy inside the Business Rules Composer and then selecting the appropriate option. Publishing locks your policy's interface and deployment makes your policy available to the rules engine runtime outside of the Business Rules Composer.

Now that you have a deployed policy, it's time to integrate it into a BizTalk Server application. As discussed earlier, the rules engine is accessible from BizTalk Server. It is actually a standalone .NET component. As a result, there is more than one way to execute your rules from within an orchestration:

  • Add a Call Rules shape.
  • Execute your policy using the Business Rules API, most likely in an Expression or Message Assignment shape.

The second option provides a little more flexibility because it allows you to specify a specific version of your policy rather than automatically using the most recent version. We'll be exploring the more common Call Rules shape in this paper but see the SDK documentation for the Microsoft.RuleEngine.Policy class if you need to dig into the API option.

The Call Rules shape enables us to call out to a specified policy, passing in the facts that the policy consumes as parameters. These parameters are assigned to messages and variables in our orchestration.

The code that accompanies this paper includes a sample BizTalk Server application with a simple orchestration that makes use of the Call Rules shape. The following figure shows the orchestration.

Figure 2 Example application orchestration

Sample application orchestration

Here's an overview of what's going on in the orchestration.

  1. Pick up an applicant's details message from a receive location.
  2. An orchestration variable contains a reference to a RiskProfileHelper object.
    An Expression shape is used to set the ApplicationID property to match the incoming applicant details message
  3. Use the Call Rules shape to call into our policy. The facts that our policy needs are tied to messages and variables defined in our orchestration. If you right-click on a Call Rules shape and choose Configure…, you are presented with the dialog box that appears in the following figure.
  4. Get the completed risk profile from our RiskProfileHelper. This is assigned to a message which is sent out using a send port.

Figure 3 CallRules policy configuration dialog box

Configuring a policy with the Call Rules shape

As you can see, adding and configuring a Call Rules shape to an orchestration is very straightforward. There are a couple of things that you need to be aware of:

  • The Call Rules shape must be placed into an atomic scope or orchestration
  • You need to be careful when configuring your facts to ensure that the Document Type name that you supply in the Business Rules Composer is identical to the message type name as it appears to BizTalk.

To see the orchestration above in action, build and deploy the supplied BizTalk Server application and bind the send and receive ports as required on your machine. We used simple file send and receive locations for this. Assuming that you're doing the same, drop an example applicant details file into the receive location to activate the orchestration. The code that accompanies this paper includes sample XML instance files in the XmlInstances folder. After some processing, you should see a new risk profile appear in your file send location.

Rules engine execution can be tracked in the Health and Activity Tracking (HAT) tool alongside orchestrations, messages, and pipelines. To enable tracking for a policy, open up Configuration | Policies on the HAT main menu and select the policy of interest. From here you can specify the type of tracking that you wish to enable or disable for the selected policy. The great thing here is that policy tracking information is available through the regular message flow screen alongside the standard message tracking details. If policy tracking information is available for an orchestration instance, a new link appears that takes you to the relevant tracking information. The categories of data here are governed by the tracking options that you enabled for the policy that got executed. For example, the following figure shows some tracked fact activity.

Figure 4 HAT policy execution tracking

Example of tracked fact activity

When you work with the Business Rules Composer, you're loading and saving policies and vocabularies from a specified SQL Server database. Typically, this is your local BizTalk development database or a shared development database. You might be wondering how you move those policies and vocabularies from that machine to a new location. For example, you might want to move a policy from a development database to a testing database or maybe export your policy out to a file which you can then check into your source control system. The answer, as you might have guessed, is the Rules Engine Deployment Wizard.

The Rules Engine Deployment Wizard is a simple tool but one that you'll definitely need to use. It offers the following functionality:

  • Export a policy or vocabulary from SQL Server to an XML file
  • Import and publish a policy or vocabulary from an XML file into SQL Server
  • Deploy a policy
  • Undeploy a policy

The code supplied with this paper contains both a vocabulary and a policy. To make use of these files you'll need to first add the referenced RiskScoringHelper assembly into your GAC. You can do this either by building the assembly (if you're using the C# version of the code) or by running the supplied batch file. You can then use the Rules Engine Deployment Wizard's import functionality to import the vocabulary and policy files into your chosen BizTalk SQL Server rules engine database. Import the vocabulary file (RiskScoring_Vocabulary.xml) followed by the policy file (RiskScoring_Policy.xml). Finally, you need to deploy the policy. You can either do this from the Business Rules Composer (right-click on the policy and choose Deploy) or use the deploy feature that the wizard provides.

The goal of this paper is to give you an insight into the power and flexibility of the Business Rules Framework and maybe given you a few ideas about how you could incorporate business rules into one of your projects. The scenario presented here looked at a specific, fictional business but the requirements that the article explored – run a set of rules against an input document to produce a customized, flexible output document – are pretty common. Finally, the features explored here are by no means all that the Business Rules Framework has to offer; for more information you should fully explore the SDK and product documentation.

Was this page helpful?
(1500 characters remaining)
Thank you for your feedback
Show:
© 2014 Microsoft