All,
I have been working on property get based rules. As part of this work I have been developing unit tests, and wanted to share with you some examples and results to help you understand in a simple way how these rules affect execution flow and can improve performance.
Here is a simple case with Milk and Store classes:
Milk Class |
{ public decimal Percent { get; set; }
public string Brand { get; set; }
public decimal Gallons { get; set; }
public decimal Fat { get; set; }
/// <summary> /// Calculates the amount of fat represented by the current milk instance. /// </summary> static Rule CalculateFat = Rule<Milk>.Calculate( m => m.Fat = m.Gallons * m.Percent); }
|
Store Class |
{ public ICollection<Milk> Milks { get; set; }
public decimal MilkGallons { get; set; }
public decimal MilkFat { get; set; }
/// <summary> /// Calculates the amount of fat represented by the current milk instance. /// </summary> static Rule CalculateMilkFat = Rule<Store>.Calculate( s => s.MilkFat = s.Milks.Sum(m => m.Fat)); }
|
And the following test method:
Unit Test |
// Construct Test Instances { Milks = new List<Milk>() { new Milk() { Gallons = 0.5m, Percent = 0.02m }, new Milk() { Gallons = 1m, Percent = 0.005m } } }; // First Assert Assert.AreEqual(0.015m, store.MilkFat);
// Change Property store.Milks.First().Gallons = 1;
// Second Assert Assert.AreEqual(0.025m, store.MilkFat);
|
The following table shows the rule execution timing if these two rules are marked with either property change or property get invocation types.
Property Change | Property Get |
Construct Test Instances { Milk.CalculateFat {} Milk.CalculateFat {} Milk.CalculateFat {} Milk.CalculateFat {} Store.CalculateMilkFat {} }
First Assert { }
Change Property { Milk.CalculateFat {} Store.CalculateMilkFat {} }
Second Assert { }
|
Construct Test Instances { }
First Assert { Store.CalculateMilkFat { Milk.CalculateFat {} Milk.CalculateFat {} } }
Change Property {}
Second Assert { Store.CalculateMilkFat { Milk.CalculateFat {} } }
|
So in the property change scenario, the rules rule every time any change is made immediately, which means by the time the first assert occurs, all of the rules have already run, and have run 5 times. In the property get scenario, the rules do not run until the first assert, and then only run 3 times.
This is a simple example showing flow. More complex scenarios are best described using diagrams. Consider the following tree of calculated properties in a hierarchy, where each node represents a property on a concrete instance, and each line represents a dependency:

Let’s assume that some global factor, denoted by the (red) node, changes. In a property change scenario, this will cause each dependent node, the (purple) nodes, to trigger property change rule invocation. These nodes in turn will trigger the (blue) nodes, which will finally trigger the topmost (green) node to calculate. Fortunately, property changes that occur as a result of rule invocation are queued. This means that instead of racing up the graph once for each (purple) node up to the (green) node, the rule invocations will be deferred until previously triggered rules have run, resulting in the (green) node updating only once as the last rule to run. If you are making significant graph changes yourself and do not want property change rules to run until you are finished, you can accomplish the same thing by performing these changes inside a using (new GraphEventScope()) { } block, which will cause rules triggered due to property changes to queue until your scope of work is done. In the example above, I could have done this to prevent the Fat property from being calculated twice when initializing each Milk instance.
The property get approach does not change the number of rule executions in this scenario, but it does change the sequencing. Instead of the (purple) nodes calculating first, they instead raise property change notifications (not graph events, just path change notifications) that trigger paths being watched by other rules, and also mark themselves as dirty (aka, needing to recalculate if invoked). This triggering proceeds up to the (green) node, marking all nodes as dirty (pending rule invocation) but not executing a single rule. If another property change rule, the UI, ExoWeb scope queries, or you own code were to access the (green) node, rule execution would flow top down as each node is accessed. However, if the triggered nodes were never used, the rules would not rule. This is the ideal case for calculated properties that are not persisted.
Right now the unit tests for ExoRule, though basic, are passing. I am now going to start on ExoWeb tests to ensure that the scope of work pattern we put in place effectively triggers these property get rules to ensure changes are sent back for calculated properties for scope of work instances. Once complete, we should be able to start being able to use these types of rules on our projects.
Thanks,
Jamie
Hi Jamie,
Thanks for this well written description.
Here’s my feedback:
1) Do you think Rule<T>.Calculate() would be clearer if it were named “Calculation”, or some other noun rather than a verb?
2) With regard to schedule, how close are you to a check in? The current Enrich iteration ends on Tues 6/28 and our testing is taking place this week and next week. I hope we will have a stable release on Weds 6/29.
3) If you’d like to test this against Enrich in a low risk way, you can shelve your changes and run the shelved code against the autotest VM’s to see if all tests are passing. This can also help measure the performance impact of your changes. I can help you with this if you’d like.
Thanks,
Matt