Charles Young

  Home  |   Contact  |   Syndication    |   Login
  196 Posts | 64 Stories | 510 Comments | 373 Trackbacks

News

Twitter












Article Categories

Archives

Post Categories

Image Galleries

Alternative Feeds

BizTalk Bloggers

BizTalk Sites

CEP Bloggers

CMS Bloggers

Fun

Other Bloggers

Rules Bloggers

SharePoint Bloggers

Utilities

WF Bloggers

Beta 1 of Windows Workflow Foundation (WF) has been released and can be downloaded from here.   I was fortunate to have access to an earlier beta version a few months ago, and it has been interesting to study the differences.   One major change is how rules are handled.   The earlier non-public beta used the Microsoft Business Rule Engine (MS BRE) to execute rulesets within workflows.   Beta 1, however, does not ship with the MS BRE.   Instead, WF rules are executed through a sequential, rather than an 'inferencing' engine.   Although WF rule processing bears a superficial resemblance to rule processing in the MS BRE, there are profound differences.   This article attempts to explain the fundamental differences, and to provide some insight into the strengths and weaknesses of each approach.   Please remember that this article refers to Beta 1 of WF.   Things may change before final release.

WF workflows centre on the concept of 'activities'.   Each activity is defined by a class.   WF provides a base activity class called 'Policy' which is sub-classed by developers in order to implement rulesets and to processes the rules in those rulesets.   The Policy activity uses an internal ‘executor’ class to execute rulesets.

In WF, the executor executes a ruleset in a very direct fashion.   A 'Policy' activity hands its ruleset to the WF rule executor which then creates a sorted list of 'RuleState' objects.   Each RuleState represents an individual rule, and maintains state for that rule.   The list of RuleState objects is sorted by rule priority.   Each rule is then executed in sequence.  If two or more rules have the same priority, the execution order for these rules should be considered to be arbitrary, though it actually depends on the order in which rules were added to the ruleset.   Each rule can have two sets of actions.   One of these sets is executed depending on the overall evaluation of the rule conditions.   This allows rules to exhibit if...then...else semantics.   The executor further detects situations where actions perform changes to an activity property.   Property updates lead to a form of 'forward chaining' which I will describe later.

  

In MS BRE, the executor does not execute rulesets in such a direct fashion.   Rather, it executes an in-memory discrimination/join network called a 'Rete' (pronounced 'ree-tee')[1].   A Rete is a 'compiled' representation of a ruleset, but the representation is very different from a simple ordered collection of rules.   The MS BRE executes a Rete by 'asserting' a number of initial 'facts' to the root node of the network.  These facts result in the creation of 'Working Memory Elements' (WMEs) which are then filtered through the subsequent nodes.  At the far end of the network, WMEs that successfully pass all the conditions arrive at Terminal nodes.  Terminal nodes reflect individual rules, and for each complete set of required WMEs that arrive at a Terminal node, the engine adds a 'production' onto   an ‘agenda’.   For our purposes, we can think of a production as a list of actions specified as part of a rule.   If the engine finds several matches between asserted facts and the conditions in a single rule, several corresponding productions will be added to the agenda for that single rule.   The productions on the agenda are ordered according to rule priority, and then each production is executed in sequence.   Some actions perform recognised updates to facts already asserted to the working memory.   Others may assert new facts, or re-assert existing facts.   Yet others may remove facts from the working memory.   Again, this leads to forward chaining.

  

From the brief descriptions above, it may appear that the MS BRE simply represents a more complex and involved approach to achieving the same result.  For simple rule processing, this is not far from the truth.   However, there are some important differences.   For the purpose of this discussion, the most important is the nature and handling of 'facts'.   WF does not use the terminology of facts.   Instead, it executes rules against properties and methods (including static methods) of the 'Policy' activity.  [UPDATE]  This has changed.   Originally, the intention was that the Policy activity would perform rule evaluation directly on the state (e.g., the properties) of that activity.   In later betas (and I believe in the final release version), Policy activities are now designed to perform rule evaluation on state held at the workflow level, rather than at the policy level.   Our initial use of WF (at late beta stage) suggests that this is a quite natural model.  References to these members are represented by instances of the CodeDOM CodePropertyReferenceExpression and CodeMethodReferenceExpression classes which are then used as terms within rule conditions, actions and updates.  Each ruleset, therefore, has available to it a discrete and pre-determined number of data.   At run-time, there is no direct mechanism for adding or removing individual items within this data.   It is only possible to update the values, in which case the executor may re-evaluate rules in a forward-chaining fashion.   Put another way, we can say that, in WF, the executor performs rule actions and updates directly against application-level state.   Rules work directly on activity members.

This is all in sharp contrast to the MS BRE.   In common with other Rete-based inferencing engines, MS BRE recognises a more sophisticated 'fact' model.   A fact has a type, and is a collection of one or more values.  MS BRE always uses .NET objects as facts (specialised classes are used to represent XML or ADO.NET dataset facts), and the data values are represented as properties.  In this respect, facts in MS BRE can be thought of as 'unordered'.   In unordered facts, values are associated with named properties called 'slots'.   Many Rete-based engines recognise both ordered and unordered facts, but MS BRE effectively only recognises ordered facts.

MS BRE elaborates the basic concept of a fact by allowing rule conditions and actions to invoke methods as well as properties, including static and 'void' methods.   In this respect, MS BRE rules act on individual facts in much the same way as WF rules act on the Policy activity.   One way to think about this is to consider that the Policy activity in WF is effectively a single fact asserted to the executor.   There is no facility for asserting multiple activities or retracting activities.   In the MS BRE you can assert an arbitrary number of facts to the engine initially and then assert new facts, re-assert existing facts or retract facts.  MS BRE also has the concept of fact updates which are similar to re-asserts but which reduce the likelihood of entering rule execution loops.   Facts can be of many different types, and multiple instances of facts of the same type can be asserted.

When thought of in these terms, you can see that the MS BRE is significantly more sophisticated than WF rules.  The ability to assert multiple facts of the same or different types leads directly to the ability of the MS BRE to specify joins between facts within its rules.   WF rules have no equivalent facility.   One scenario where the MS BRE facility is very useful is where, in order to infer a solution to a given problem, it is necessary to undertake highly combinatorial processing of facts.   By 'combinatorial', we mean that, in order to solve a problem, we need to test many combinations of facts.  For example, imagine an application where we need to infer the most efficient daily schedules for a team of engineers who visit, individually, various client premises.   We may have several rules we need to apply in order to find a solution, including rules based on the location of each client site, the estimated time the engineer will spend at each site, the availability of parts required by the engineer, etc.  It is likely that part of the processing required to determine the best schedules will involve evaluating a number of different combinations of facts, eliminating the less efficient or unworkable combinations until we are left with the most efficient.   The MS BRE is far better suited to solving this type of problem than WF rules.   In MS BRE we can assert as many facts as we need to the engine.   Any one rule may have conditions which combine and compare an instance of one fact type against an instance of another, or even two (or more) instances of the same fact type.   The MS BRE will automatically evaluate every possible combination of the relevant facts asserted within its working memory by matching them against this rule.   In WF, you would need to push this combinatorial processing back into custom code within your Policy activity (or within some 'helper' object).   You code would require nested looping and conditional processing, and might even need to use techniques such as recursion.   This type of code tends to be quite hard to stabilise, debug and maintain.   More than that, the rules that govern the combinatorial processing are no longer explicit, but implicitly 'hard-wired' into your custom code.   Changes to these rules will typically require re-deployment of new versions of your .NET assemblies, unless you build your own 'mini' rule engine to act on some form of external ruleset.

Given this difference, you might wonder why Microsoft has chosen the simpler approach for processing rules in WF.  There are a number of very good reasons.   MS BRE's sophistication comes at a price.  Designing rules that handle multiple facts and fact types by selecting productions onto an agenda through a process of pattern matching is significantly more complex than designing rules that execute sequentially against a single, fixed sized, data set.   As many BizTalk developers have discovered, trying to implement anything but the most trivial rulesets in MS BRE can require a very steep learning curve.   Consider that any one rule in MS BRE may result in multiple productions being added to the agenda, depending on what facts are asserted in working memory.   You could think of these productions as being 'instances' of the one rule.  MS BRE's pattern-matching approach results in a degree of de-coupling between the rules defined by the developer and the actual actions undertaken by the engine.  This, in turn, requires the developer to design their rulesets in a non-procedural fashion.   Developers used to thinking procedurally can find this pattern-matching approach very difficult to understand and exploit.

Again, consider the problem of differentiating between multiple facts of the same type within a single rule.  MS BRE allows rule designers to associate an 'instance number' with terms that reference a given fact type.  For example, a rule condition might test to see if MyFact(1).x == MyFact(2).x (the numbers in parenthesis are the instance numbers).   This condition would result in every possible combination of one instance of MyFact being evaluated against another.   This is very powerful, and quite straightforward when understood, but is difficult for MS BRE novices to grasp.  WF avoids this complexity by operating against a fixed set of activity properties.

The simple sequential model of rule processing adopted by WF is less flexible and powerful than MS BRE, but is much simpler to implement, and more 'procedural' in nature.   This is ideal for the majority of true-life rule-processing scenarios encountered in workflows where rules are used to make simple decisions, possibly controlling the flow of the workflow.   WF is designed to bring workflow to the masses, providing a comprehensive foundation for handling stateful, long-running asynchronous processes that interact with the widest variety of external systems.  The application of rules is important within workflows, and Microsoft has traded simplicity against sophistication, providing an approach to processing rules which is simple to understand and exploit, and which will feel 'natural' to almost all developers and 'power users'.

Forward Chaining
This discussion would be incomplete without describing the approaches in each technology towards forward-chaining.   Forward-chaining is closely related to the above discussion, and is a data-driven approach to inferencing.    In a forward-chaining scenario, rule actions operate on an initial data set, and rule actions change the values of the some items within that data set.   This causes the engine to automatically re-evaluate the relevant rules against the new values, which in turn can lead to further data changes and rule re-evaluation.   The engine continues to cycle in this fashion until no further rule re-evaluation is required, at which point it is deemed to have arrived at a conclusion.   The conclusion is typically represented by the final state of the data set.

Both WF and MS BRE claim to be forward-chaining engines.  There are, however, some important differences.   WF provides simple sequential rule processing.   It is not a true inferencing engine, and its 'forward chaining' mechanism is in fact merely an elaboration of the basic sequential and 'procedural' nature of technology.

Forward Chaining in WF
In WF, the rules act directly on the sorted list of RuleState objects which represent the rules in the ruleset.  WF executes the 'then' or 'else' action list of each rule in turn, depending on the outcome of the rule conditions.   Each RuleState has a 'Pending' flag which is initially set to 'true'.   As each rule is processed the Pending flag is set to false to indicate that the rule conditions have been evaluated.   The flag is set regardless of any actions which may, or may not, be 'fired'.

The first time a 'then' or 'else' action list is processed, WF analyses the actions and determines if any of the actions invoke members of the Policy activity and have 'side effects'.   A 'side effect' means that the the invoked member may alter the state of the Policy activity in a way that could affect the evaluation of conditions in other rules.   If 'side effects' have occurred, WF immediately searches the collection of RuleState objects looking for rules whose conditions are affected by the state change and whose 'Pending' flag is currently 'false' (indicating that the rule has already been evaluated). 

Unfortunately, the forward chaining mechanism in Beta 1 does not work correctly.  I have it on good authority that the problem has been fixed in Beta 2.   In Beta 1, the WF executor selects the first rule it finds and changes the 'Pending' flag to true.  However, it fails to continue to search for subsequent 'affected' rules.   At most, just one rule is marked for re-evaluation.  In future releases, the forward chaining mechanism will re-evaluate all previously evaluated rules affected by changes to Policy activity property values.  This means that there is currently a degree of guess-work in describing how WF forward chaining will work in the final release.   Nevertheless, I think we can make a sensible attempt to describe the mechanism as it is meant to work.

If WF finds an affected rule with a higher priority than the rule whose actions caused the 'side effect', this higher-priority rule is immediately evaluated and its actions are executed.   Evaluation and execution then continues from that higher priority rule.   If there are no higher-priority affected rules, WF continues to process rules in order from the rule that caused the 'side effects'.   In WF, forward chaining is closely aligned to rule prioritisation, and changing a Policy activity property in a lower-priority rule can cause the executor to 'loop back' to affected higher priority rules.

Consider the following example:

Initial property values:

            MyPolicy.x = 0;
      MyPolicy.y = 100

Rule 1:
(Priority 10)

      IF
         MyPolicy.x > 20
      THEN
         Console.WriteLine("Rule 1 actions were executed.");

Rule 2:
(Priority 9)

      IF
         MyPolicy.x > 10
      THEN
         Console.WriteLine("Rule 2 actions were executed.");

Rule 3:
(Priority 8)

      IF
         MyPolicy.y == 100
      THEN
         MyPolicy.x = 50;

Rule 4:
(Priority 7)

      IF
         MyPolicy.x == 50
      THEN
         MyPolicy.x = 15;

MyPolicy.x initially has a value of 0.   WF evaluates each rule in priority order.   Rule 1 is processed, and its Pending flag is set to 'false'.   No actions fire because the condition is false and there are no 'else' actions.  Rule 2 is processed in exactly the same fashion as Rule 1, with the same results.   Then Rule 3 is evaluated.  In this case, the condition is true.  The Pending flag is set to 'false' and the action is executed.   This changes MyPolicy.x to 50.  The engine immediately searches for the highest priority non-pending rule whose conditions are affected by the change.   This is Rule 1.   The Pending flag for rule 1 is set to 'true', and because it is a higher priority rule than Rule 3, it is re-evaluated immediately.   This time, its action is executed.   WF then searches from Rule 1 forwards for the next rule whose Pending flag is true.   Because of the bug in Beta 1, this is currently Rule 4, but in future releases it will be Rule 2 because the executor will have detected and marked all affected rules as Pending.

The executor continues to work through the sorted ruleset, evaluating all Pending rules.   It skips Rule 3 and reaches Rule 4 which is evaluated.   The Pending flag for Rule 4 is set to 'false' and its action is fired.   The engine again searches the ruleset for affected rules, starting with the highest priority rule.   It finds that Rule 1's condition is affected by the change, and so again marks it as Pending and the re-evaluates it.   This time the action does not fire.   In Beta 1, Rule 2 and all subsequent rules are then skipped, and execution stops.  In later versions, Rule 2 will also now be Pending, and will be re-evaluated.  In this case its action will 'fire'.

WF rules are processed in a strictly sequential fashion. The forward chaining mechanism does not fundamentally change the sequential nature of WF rules.   It simply controls the Pending flag on RuleState objects and can cause the executor to loop back to a higher priority rule.   An interesting aspect of this is that for simpler rulesets, forward chaining can often be avoided by simply re-prioritised the rules so that they are evaluated in a different order.  For example:

Initial property values:

      MyPolicy.x = 0;
      MyPolicy.y = 100

Rule 3: (Priority 10)

      IF
         MyPolicy.y == 100
      THEN
         MyPolicy.x = 50;

Rule 4:
(Priority 9)

      IF
         MyPolicy.x == 50
      THEN
         MyPolicy.x = 15;

Rule 1:
(Priority 8)

      IF
         MyPolicy.x > 20
      THEN
         Console.WriteLine("Rule 1 actions were executed.");

Rule 2:
(Priority 7)

      IF
         MyPolicy.x > 10
      THEN
         Console.WriteLine("Rule 2 actions were executed.");

In this case, no forward chaining occurs, but the state of the Policy activity at the end of the processing is identical to the first example.  If you find that your rules are chaining, always analyse them to see if you can eliminate chaining by changing the rule order.  If you study the 'AdvancedPolicy' technology sample provided with the beta 1 WF SDK, you will see that chaining has been 'artificially' induced by setting the priority of Rule 3 to -5.   This reduces Rule 3 to a lower priority than any other rule, and hence, if its actions are executed, it causes a re-evaluation of Rule 4.   If, however, its priority had not been set to -5, there would be no chaining, and the ruleset would behave as expected.   The rules would, in that case, all have the same priority and would be evaluated in the order they are created and added to the discountPolicy ruleset.   Be very careful, here.   Strictly, the order in which rules of the same priority are evaluated is 'arbitrary'.   You should, as a general rule, use priority liberally in WF to control evaluation order.   One fundamental difference between WF and MS BRE is that in WF, priority governs the order of rule evaluation, as well as rule action execution.   In MS BRE rule priority only governs rule action execution.

There is one other aspect of WF's forward chaining model that must be clearly understood.  MS BRE allows forward chaining to be controlled in an explicit fashion through the use of working memory verbs (a.k.a. 'engine control functions') contained within rule actions.  These verbs are 'Assert', 'Update', 'Retract' and 'RetractByType'.   If an action simply changes the value contained in a fact, but does not re-assert or update that fact, no forward chaining occurs.   By contrast, WF uses some clever CodeDOM-based code to analyse each rule and determine every situation where Policy activity property values are changed.   It is true that WF has an 'Update' verb represented by the RuleUpdateAction class.  However, its use is quite different to MS BRE.   This action is used in rule action lists to invoke 'forward chaining' in situations where the value of a Policy activity property has not actually been changed.

WF's 'implict' model of forward chaining means that re-evaluation of rule conditions occurs very readily, and this presents its own problems.   Consider the following single-rule example:

Rule 1:  

      IF
         InvoicePolicy.ShippingCharge < 2.5
      THEN
         InvoicePolicy.ShippingCharge = 0;

This may appear to be a very straightforward rule.  However, it will cause 'forward chaining' to occur, and WF will enter a continuous loop, repeatedly re-evaluating this single rule.   In order to handle this, WF provides loop detection facilities, forcing re-evaluation to stop once a loop count has reached a configurable maximum value.   Unlike MS BRE, you configure the maximum value for rule execution at the rule level, rather than at the ruleset level, using the MaxExecutionCount property of the Rule class.   The default value (0) is used to indicate unlimited execution.

As we have seen, WF rules work directly against explicit application state, and do not perform pattern matching against an arbitrary number of facts.   The 'forward chaining' mechanism in WF will therefore typically be used in conjunction with flags and indexes, and will often compute values held in arrays of collections.   Consider the following example:

Initial property values:

      OrderLineIdx = 0
      OrderLines = [collection of order line properties]

Rule 1:
  set_discounts_upper

      IF
         OrderLineIdx < OrderLines.Count
         AND OrderLines[OrderLineIdx].Value >= 100
      THEN
         OrderLines[OrderLineIdx].Value *= 0.9 
         OrderLineIdx++

Rule 2:
  set_discounts_lower

      IF
         OrderLineIdx < OrderLines.Count
         AND OrderLines[OrderLineIdx]. Value < 100
      THEN
         OrderLines[OrderLineIdx].Value *= 0.95 
         OrderLineIdx++

Rule 1 applies a 10% discount to each order line whose value is >= 100, and Rule 2 applies a 5% discount on all other order lines.  The rules use a Policy activity property (OrderLineIdx), together with forward chaining, to iterate through a collection of order lines.   Note that the rules implicitly assume that the value of OrderLineIdx is 0 before the rule is first evaluated.

Note that this rule cannot be implemented correctly in the current beta version of WF because of the bug described earlier.  You may wonder if we could simplify the rules above into a single if…then…else rule.   However, this is not possible.  The reason is that we use OrderLineIdx in the conditions of both rules to control the number of forward-chaining cycles.   If we tried to create a single rules as follows…

      IF
         OrderLineIdx < OrderLines.Count
         AND OrderLines[OrderLineIdx].Value >= 100
      THEN
         OrderLines[OrderLineIdx].Value *= 0.9 
         OrderLineIdx++
      ELSE
         OrderLines[OrderLineIdx].Value *= 0.95 
         OrderLineIdx++

…the WF rule executor would either encounter an error when OrderLineIdx goes out of range or enter a never-ending loop, depending on the implementation of the OrderLines collection.  If the error is silently consumed, the rule will continue to fire the ELSE actions for ever due to the incrementing of OrderLineIdx.

Now consider a more complex problem.   Let’s say we have a rule that states that we apply the same discounts as in the rule above, unless the entire value of the order, after discounts have been taken into account, is less than 250, in which case, we apply 5% discounts to every order line.  We might implement three rules to provide us with the correct answer.

Initial property values:

      OrderLineIdx = 0
      OrderLines = [collection of order line properties]*
      UpperDiscountThreshold = 100
      UpperDiscount = 0.1
      LowerDiscount = 0.05

* Each order line object will maintain various properties, including an OriginalValue property, which holds the value of the order line before any adjustments, such as discounts, and a Value property which holds values after adjustments.   Initially, these two properties will contain the same value.

Rule 1:
  set_discounts_upper (priority 1)

      IF
         OrderLineIdx < OrderLines.Count
         AND OrderLines[OrderLineIdx].OriginalValue >= UpperDiscountThreshold
      THEN
         OrderLines[OrderLineIdx].Value = OrderLines[OrderLineIdx].OriginalValue * (1 – UpperDiscount)
         OrderLineIdx++
         Update OrderValue

Rule 2:
  set_discounts_lower (priority 1)

      IF
         OrderLineIdx < OrderLines.Count
         AND OrderLines[OrderLineIdx].OriginalValue < UpperDiscountThreshold
      THEN
         OrderLines[OrderLineIdx].Value = OrderLines[OrderLineIdx].OriginalValue * (1 – LowerDiscount)
         OrderLineIdx++
         Update OrderValue

Rule 3:
  check_upper_discount_threshold (priority 0)

      IF 
         UpperDiscount == 0.1
         OrderValue < 250
      THEN
         UpperDiscount == 0.05
         OrderLineIdx = 0

Rules 1 and 2 are very similar to the previous example.   The differences are that we now use Policy activity properties to provide values for the upper discount threshold and the upper and lower discounts, and we calculate the discounts slightly differently.  Also, note the use of the Update action.   OrderValue would probably be implemented to calculate the total order value, including discounts, dynamically by iterating through the collection of order lines.  We therefore don’t change the value of this property directly.   However, because it is used in the condition of Rule 3, we need to signal to the engine that its value has been updated by the actions in Rules 1 and 2.

Rule 3 controls forward chaining.  If the total value of the order is less than the threshold, it sets the upper discount to 0.05 and resets the OrderLineIdx property to 0.   Because OrderLineIdx has been changed, future versions of WF will re-evaluate Rules 1 and 2, and will recalculate the discounts for each order line.

In this example, we must initially evaluate Rules 1 and 2 before Rule 3, because Rule 3 takes into account the initial application of discounts.   We cannot re-order the sequence of rules to solve the problem.

Once again, I must point out that, due to the Beta 1 forward chaining bug, it is not currently possible to create a working implementation of these rules in WF.

[UPDATE] I recently revisited this example using a later beta of WF, and, having now implemented the code successfully, I can confirm that that the forward chaining mechanism works just as I expected and have described.   

Forward Chaining in MS BRE
Forward chaining in MS BRE is different to WF.   The up-front difference, as we have seen, is that it is controlled explicitly by asserting, updating or retracting facts.   The underlying difference is that MS BRE uses a pattern-matching approach to evaluating rule conditions against the facts in working memory, and selects 'productions' onto a prioritised agenda based on the matches it finds.   WF has no equivalent pattern-matching approach, and evaluates rules in a sequential fashion.   WF's 'forward chaining' facility is really just a mechanism that allows rules to be re-evaluated sequentially if a rule changes property values in the Policy activity.

Using MS BRE we can solve the same discount problem described above in a fairly similar fashion.   In this case we will create classes to represent orders and order lines.   We will assert a single instance of Order and multiple instances of OrderLine to the rules engine, together with a single instance of Discount.   The Discount class only contains static members, but we still have to create and assert an instance of it into MS BRE's working memory.   Note that the Order class is initialised to hold a collection containing each of the asserted OrderLine instances.   This allows the Value property to iterate over the values of the individual order lines in order to calculate the total order value.

Order

      OrderLine[] OrderLines
      double Value

OrderLine

      private Order _order
      double OriginalValue
      double Value

Discount

      static double UpperDiscountThreshold
      static double UpperDiscount
      static double LowerDiscount

In order to calculate the order value with the correct discounts, we create the following three rules.   Note that the rules are roughly equivalent to the three rules in the WF example.   It is also worth pointing out here that MS BRE has no support for if…then…else rules.

Initial Discounts property values:

      Discounts.UpperDiscountThreshold = 100
      Discounts.UpperDiscount = 0.1
      Discounts.LowerDiscount = 0.05

Rule 1
:  set_discounts_upper (priority 1)

      IF
         OrderLine.Value >= Discounts.UpperDiscountThreshold
      THEN
         OrderLine.Value = OrderLine.OriginalValue * (1 - Discounts.UpperDiscount)
         Update Order

Rule 2:  set_discounts_lower (priority 1)

      IF
         OrderLine.Value < Discounts.UpperDiscountThreshold
      THEN
         OrderLine.Value = OrderLine.OriginalValue * (1 - Discounts.LowerDiscount)
         Update Order

Rule 3
:  check_upper_discount_threshold (priority 0)

      IF 
         Discounts.UpperDiscount == 0.1
         AND Order.Value < 250
      THEN
         Discounts.UpperDiscount == 0.05
         Assert Discounts

If you compare the MS BRE ruleset to the WF example, you will see that the MS BRE rules appear a little 'simpler'.   One reason for this is because they do not contain any equivalent to the OrderLineIdx property.   In the WF example, OrderLineIdx is used to control procedural iteration through a collection or order lines.   Although the Order object in the MS BRE example contains a collection of order lines, we don't reference this collection within the MS BRE rules.   Instead, each separate OrderLine object in the collection is asserted to the engine.  

The apparent 'simplicity' of the MS BRE rules is deceptive.   These rules are more declarative than the WF rules, and it is precisely for this reason that many developers will find them harder to understand.  We can no longer 'see' the procedural logic that ensures that discounts are applied to each order line before a determination is made in Rule 3 that we must recalculate the discount for the higher-value lines.   Let’s explain how the engine processes these rules.

Before any rules are evaluated, we assert an instance of Order, several instances of OrderLine and a single instance of Discount to the working memory of the rules engine.   I have not included example code to do this, but it is simple enough using the MS BRE's API.   As the facts are asserted, they are matched against the conditions within each rule.   Unlike WF, the sequence in which this evaluation occurs is not relevant, and is not under our control.  The Rules Engine compares the rules against all the asserted facts, and where ever it finds that the conditions match, it creates a 'production' (think of this as an instance of the rule actions) on the agenda.  

Once the engine has completed the match phase, adding productions for every match it can find, it enters the conflict resolution stage.   The order of the productions on the agenda is adjusted according to the corresponding rule priority.   Hence, all the Rule 1 and Rule 2 productions appear higher on the agenda than any Rule 3 production.  There is a bit more to conflict resolution than this, but for our purposes, we need only consider rule priority[2].

Once the agenda has been prioritised, the engine begins to work through the list of productions, executing the actions for each one.   If a production was created for Rule 1 or 2, where the rule conditions matched a specific instance of OrderLine, the first action will change the Value property of that same instance.   The second action will indicate to MS BRE that the state within the Order object has been updated.   Remember that the Value property of the Order object will calculate the current order value by iterating over its collection of order lines.   Changing the value of an OrderLine will therefore have an effect of the value of the order as a whole.

There is a subtle issue here concerning a feature of the engine called 'side effects', which allows this approach to work in MS BRE.   The ability of the Order object to re-calculate the total value from its private collection of OrderLines is a bit controversial.  Many similar engines would effectively prevent this 'behind-the-scenes' interaction between facts by working on cached values rather than on the actual objects.   Indeed, 'side effects' can be switched off at the individual condition or action level in order to bring MS BRE in-line with the 'purer' approach.  Note also that caching is always used with XML and ADO.NET facts.   Using side effects with .NET objects does pose some real dangers, but is very practical in an engine designed to work closely with object graphs.  The approach generally helps to simplify rule design.   I've pursued this argument elsewhere, and really don't want to enter into further discussion on the issue, so if you are horrified at Microsoft's default approach, I understand your position, but...well...that’s how it is.

Each time a production for Rule 1 or 2 performs an Update on the Order object, the engine immediately enters another match-resolve-act cycle.   However, it only re-evaluates Rule 3, because the conditions in Rules 1 and 2 do not refer to the updated Order object.   There will never be more than one production on the agenda for Rule 3 because there are only single instances of Discounts and Order.   Each time the rule is re-evaluated, this production, if it exists, will be removed, and then a new production may or may not be added to agenda, depending on the value of the order.   If a new production is created, the conflict resolution stage will ensure that it is placed at the end of the agenda, and won't be executed until all Rule 1 and 2 productions have been processed.

Eventually, the engine finishes processing all Rule 1 and 2 productions.   If there is a Rule 3 production on the agenda, this now 'fires'.  It changes the UpperDiscount static properties of the Discounts class and then re-asserts the Discounts object in order to initiate forward chaining.   Note that Rules 1 and 2 both refer to the Discounts class in their conditions.   For developers used to object orientation, this lack of distinction between classes and objects may seem strange.   However, consider that because MS BRE uses pattern matching, its rules directly refer to types (classes) rather than instances.  Productions, on the other hand, execute actions against object instances.   Maybe the model would be clearer if Microsoft did not support static members in actions, but this would be an unnecessary limitation on developers.

The engine now enters a new match-resolve-act cycle.  This will result in productions being added to the agenda for Rules 1 and 2, but no production will be added for Rule 3 because the Discounts.UpperDiscount property does not have a value of 0.1.   Once the productions have been added and resolved, the engine will work through the agenda, executing each production in turn.   Eventually, the agenda will be empty, and processing will stop.   The Order.Value property will report the final calculated value of the order with the correct discounts.

If you are unfamiliar with MS BRE, but have followed this description, you will have made significant headway in understanding how the engine works.  I hope this will convince you that, although the MS BRE ruleset looks simpler than its equivalent in WF, it is actually harder to understand, at least initially.   I have included a second example, here, which will take you a step further by showing you how ‘instance’ numbers can be used in rule conditions and actions to distinguish between multiple instances of the same fact type.   This allows the engine to solve combinatorial problems involving comparisons between facts of the same type.   The example I have chosen is a well-known 'NP-complete' problem in computer science that can typically only be sensibly 'solved' by an approximation approach.   My example uses a brute force approach in order to demonstrate features of the engine.   As you will see, this would not be a good idea in a real-life implementation.

Conclusions
This article has attempted to show the differences between the simple sequential approach to rule processing within WF and the more complex, but ultimately more powerful approach provided by the Microsoft Business Rule Engine.   Microsoft clearly believes (and I agree with them) that the simple sequential approach is more appropriate for the majority of scenarios encountered within workflow solutions.  For most developers and 'power users', WF rules will be much easier to understand and exploit than MS BRE rules, and far better suited to a technology whose use is expected to become ubiquitous in the Microsoft world throughout desktop/browser applications.  MS BRE rules are more declarative than WF rules, and there are whole classes of problem that are best addressed through the power of a Rete-based rules engine.   Although WF has a basic form of 'forward chaining', this mechanism lacks the power of MS BRE's forward chaining inferencing model, and consequently, WF rules lack the rich, declarative expressivity of rules in MS BRE.

Acknowledgements
Special thanks to Jurgen Willis, a program manager on the WF product team with specific responsibility for WF rules, for his help in understanding the WF rules model.   Also, thanks to my good colleague, the 'Arch Hacker' for his input.
 


[1] Rete is Latin for 'net' (or, according to my Latin-English dictionary, 'snare'!).  The Rete algorithm was developed by Charles L Forgy in 1974, and is described in a 1982 article - Forgy, C. L. (1982). Rete: A Fast Algorithm for the Many Pattern/Many Object Pattern Match Problem.   Artificial Intelligence, 19:17-37.   Its main advantages centre on the use of working memory to maintain partial matches and indexed collections of Working Memory Elements.   Retaining partial matches greatly minimises the amount of re-evaluation necessary as the executor performs match-resolve-act cycles.  The use of indexed collections of WMEs allows the executor to very efficiently remove facts from working memory.  Note that whenever facts are re-asserted or updated, they must first be removed from working memory.

[2] Rule prioritisation is often referred to as ‘salience’.  Most Rete rule engines provide a default conflict resolution strategy that combines salience with an approach where, for productions with the same salience, those productions most recently added to the agenda (and therefore associated with the most recently asserted facts) are placed at the top of the agenda and fired first.   This is called a ‘depth-first’ strategy, and is considered by some to be the most generally useful approach.  A ‘breadth-first’ strategy is where, for productions with the same salience, those productions most recently added to the agenda are placed at the end of the agenda and fired last.   Jess (a Java rules engine) and CLIPS support both depth- and breadth-first, and CLIPS additionally supports several other strategies.    MS BRE implements a depth-first approach only, and has no support for any other strategies.   [UPDATE] No it doesn’t!   I got this quite wrong, for which I apologise.   The MS BRE does not implement any conflict resolution strategy, and supports priority (salience) only.   You should treat the MS BRE as if it implemented something similar to the ‘Random’ strategy offered by CLIPS.   The actual behaviour is dictated by the very simplistic implementation o the MS BRE agenda as a prioritised queue in which, each time a production is removed from the head of the queue, it is replaced by the production currently at the tail of the queue, and the agenda is then re-prioritised.   In very simple scenarios where priority is not used, this approach can approximate to a breadth-first approach during the first forward chaining cycle, and then switch to something like a depth-first approach for subsequent cycles.   However, this co-incidental behaviour soon breaks down if priority is used, and the behaviour becomes more like a ‘random’ strategy with each successive cycle.   You should also consider that MS BRE indexes its memories based on the hash values of the .NET objects those memories contain.   This effectively pseudo-randomises the order in which evaluations are done in join nodes in the Rete network.   This is very different to the behaviour of many other engines, such as Jess or CLIPS, and emphasis the need for the rule developer to cater carefully for search strategy in the way they construct their rules.  I have yet to find this a significant problem.   I have always been able to implement the approach I needed by adding additional conditions, etc.

One of the more perplexing aspects of this type of rules engine is that, if the conflict resolution strategy is changed, the outcome of rule processing is often fundamentally altered.   Conflict resolution strategy is an engine-specific feature, and this dependency can potentially undermine the benefits of the declarative nature of rules, making it difficult, for example, to share business policies (and their corresponding rulesets) across the enterprise when using different engines.

 

posted on Sunday, October 9, 2005 11:02 PM

Feedback

# Charles Young's Article on Comparing WF rules and the Microsoft Business Rule Engine 10/19/2005 1:32 PM Deepak Lakshmanan
Charles Young has written an excellent and detailed article comparing the WF rules engine &amp; BRE....

# re: WF: Comparing WF rules and the Microsoft Business Rule Engine 10/19/2005 6:33 PM Deepak
Great Article!!!

# More WF Items 10/24/2005 1:40 AM Owen Allen (MSFT PacWest)
I'm going to be on a WF kick for the next few days, I believe, while I'm attending some WF training.&nbsp;...

# Nice article !!! 12/24/2005 2:37 PM Christof
Nice work!!!! It would even have been better Charles if you could include the code (codeDOM) for making all/some of these examples work. Especially with regards to arrays it would be interesting to see how to implement this. Keep up this very very nice work; this is the kind of articles a lot of people are looking for!

Best regards,
Christof

# Windows Workflow as a Rule Engine 8/13/2006 7:38 PM K. Scott Allen
One interesting facet to Windows Workflow is how I can combine procedural knowledge with declarative...

# re: WF: Comparing WF rules and the Microsoft Business Rule Engine 8/31/2006 2:00 PM Walter Michel
You've done it again, Charles. I find your blog to be one of the best sources of in depth BizTalk knowledge. Wonderful article!

Walter

# Windows Workflow Resources 9/22/2006 4:53 PM Natalia B.
Get the Bits:
.NET Framework 3.0 Install Bits (including the latest WF Bits)
http://www.microsoft.com/downloads/details.aspx?FamilyId=19E21845-F5E3-4387-95FF-66788825C1AF&amp;displaylang=en...

# re: WF: Comparing WF rules and the Microsoft Business Rule Engine 3/20/2007 5:47 AM Marco
Just wanted to let you know that the new Rule Manager can import BizTalk rules (or at least most constructs) and export it to a Windows Workflow Foundation solution. You can try it out (http://www.acumenbusiness.com).



# re: WF: Comparing WF rules and the Microsoft Business Rule Engine 2/16/2008 7:01 PM eric
Thanks for this great detailed article. This throws some light into newbies like myself into Windows workflow.
http://www.doyenrealty.com/

# re: WF: Comparing WF rules and the Microsoft Business Rule Engine 7/5/2008 10:42 AM shopping
Might I say that this whole idea of 'workflow' and putting it into software is such an absolutely brilliant idea. I wish I had access to it years ago when this sort of thing would have been ideal for the logistics operation I was involved with. We had to rely on some really old-school and crass software to do our stuff, and I was always convinced there had to be something better – pity our IT guy didn't seem to know much about this.


# re: WF: Comparing WF rules and the Microsoft Business Rule Engine 7/8/2008 9:20 AM Josh
Hi , first of all thanks for great articale.

its gives me lots of (new) ideas of using wf rules instead of biztalk.

the problem with biztalk bre composer is that users need to install or use it from terminal place. but with wf we can launch the rule set desgin dialog from any client application.

my question is what the best way to call the wf rule from biztalk orch(we use biztalk2006) , of course the simple option is to call to webservice from BTS that host the wf rules.

Is there any option to call the wf rule directly from the BTS orch shape? is any problem to call from .net2.0(bts 2006) to .net3.5?

Thanks,

Josh


# re: WF: Comparing WF rules and the Microsoft Business Rule Engine 7/30/2008 3:09 PM AJ Weaver
Is this blog post now out of date? It seems so and perhaps should be re-written or taken down - especially since it is a top hit on "WF rules engine" with google.

Better yet, keep your top spot and update your post based on the WF release in VS2008. (no strike throughs, etc - just the facts.)

# re: WF: Comparing WF rules and the Microsoft Business Rule Engine 7/30/2008 3:51 PM Charles Young
Thanks for the feedback.

It is out of date in the sense that it was written back when WF was still in beta, but it is not out of date in regard to explaining how the two engines work. In particular, there was a bug in the beta which meant I had to guess how things would work in the release version...and I fortunately guessed right.

I take your point though. I've been caught out before by the 'ageing post' problem. I won't make any rash promises to update this immediately, but I'll see if I can perhaps tidy it up and bring it up to date. Another point to bear in mind is that the next version of WF will introduce significant changes and improvements to the rule processing model. The new version won't ship until next year (2009), but at that time, this post will certainly be out of date.

# re: WF: Comparing WF rules and the Microsoft Business Rule Engine 7/30/2008 3:54 PM Charles Young
... and further to the above, the 'strikeout' was just my way of keeping me honest :-) I made a wrong statement about MS BRE in the original version of the post, and was simply 'coming clean'. I try to get things right first time, but I don't always succeed.

# re: WF: Comparing WF rules and the Microsoft Business Rule Engine 8/25/2008 7:03 PM Jo Presse
Nice Article - thx for sharing!

The first time a then action list is processed, WF analyses the actions and determines if any of the actions invoke members of the Policy activity and have side effects.

# re: WF: Comparing WF rules and the Microsoft Business Rule Engine 9/21/2008 12:32 PM andreyminaev
Thanks for this great detailed article.

# re: WF: Comparing WF rules and the Microsoft Business Rule Engine 11/12/2009 5:05 PM Lijfrente berekenen
I was wondering if the forward chaining mechanism does change the sequential nature of WF rules. I couldn't find anyone to provide me with a good answer. In this article I found more information that I was looking for:-)
If I understand your article correctly the answer is that the forward chaining mechanism does not fundamentally change the sequential nature of WF rules. Thanks!
Regards, Juan


# re: WF: Comparing WF rules and the Microsoft Business Rule Engine 11/12/2009 7:22 PM Charles Young
Correct, Juan. Strictly, the term 'forward chaining' describes an approach to reasoning within a rule-based system. Whenever relevant facts are asserted to, retracted from or changed within a forward chaining rule engine, the engine immediately re-computes its state by exhaustively finding all new matches and discarding any old matches that are no longer valid. A forward chaining engine keeps on repeating the cycle of firing rules and, if those rules assert, retract or modify any facts, re-computing the matches immediately before firing the next rule. It carries on until there is no longer any work to do, and at that point the assumption is that the engine has reached its 'goal'.

WF Rules provides this facility within the strict constraints of a sequential (one might almost say procedural) engine that operates on a single 'fact' (i.e., the workflow). This is a very different environment to a set-based 'production' system that operates on many facts at once. Because WF rules are evaluated and fired sequentially, forward chaining can potentially kick in every time a rule is fired. If that rule changes the value of a field or property of the workflow, the forward chaining mechanism immediately re-evaluates any previously evaluated rules that are affected. Because WF is a sequential engine, the forward-chaining mechanism is essentially a way of looping back through some previously evaluated rules.

The main role of this mechanism in WF is to allow rule declaration to be more 'declarative' by reducing the need to order rules in an exact way. However, the mechanism can also be used to support certain forms of reasoning. Compared to a set-based production engine, these facilities are limited, but still welcome.


# Very helpful Article ! 2/10/2010 2:00 PM Tim Sonnenschutz
Thanks a lot for sharing these information about MS Business Rule Engine.Your detailed example helped me to solve a problem in the code :-) And I was looking a long time for that...

# re: WF: Comparing WF rules and the Microsoft Business Rule Engine 4/25/2013 5:55 PM Andre Torkveen
Came across this article NOW (April '13), but found it quite read-worthy, even generations later! Congratulations and thanks! :-)

Post A Comment
Title:
Name:
Email:
Comment:
Verification: