BusinessRules not BrokenRules...

BusinessRules not BrokenRules...

Old forum URL: forums.lhotka.net/forums/t/10800.aspx


jh72i posted on Monday, October 24, 2011

I’m way behind on using the new  BusinessRules approach introduced, oh, years ago. I’m still with the simple Assert approach that has a simplicity I love.

 

Background:

I have created my own rules management bits and pieces probably 7/8 years ago to intro concepts like severity, (efficient) resource management for localization, etc. as well as many shortcut common rules like length, max, min, range, greaterthan, lessthan, etc., etc. This inherits/wrap the clsa implementations so I get all that good stuff too.

 

My BusinessBaseEx class is an abstract switchable (derives some behaviour from usage context) class that implements an IsBroken method (public virtual bool IsBroken() / public override bool IsBroken(params string[] ruleNames)). This is overridden by inheriting classes to implement their own specific rules using those old-style using Asserts:

 

public override bool IsBroken(params string[] ruleNames)

{

       bool exceptLazyLoad = ContainsRule(Rules.AllExceptLazyLoads, ruleNames);

       bool allProperties = (ruleNames.Length == 0 || ContainsRule(Rules.All, ruleNames) || exceptLazyLoad);

 

if (allProperties || ContainsRule(Rules.Name, ruleNames))

{

       BrokenRules.AssertRequired(Rules.Name, _name);

       BrokenRules.AssertMaxLength(Rules.Name, _name, 50);

 

The property settinger then include what rules to check on setting a new value:

 

[SmartProperty(Required = true, MaxLength = 50)]

public string Name

{

       get { return _name; }

       set

       {

              this.SetProperty("Name", ref _name, value, Rules.Name);

       }

}

 

Creating dependencies can be achieved simply by including more rules to check in the setters:

 

this.SetProperty("Name", ref _name, value, Rules.Name, Rules.AvailableSpace);

 

And then doing the needful in the IsBroken - silly example here:

 

if (allProperties || ContainsRule(Rules.Name, ruleNames))

{

       BrokenRules.AssertRequired(Rules.Name, _name);

       BrokenRules.AssertMaxLength(Rules.Name, _name, _availableSpace.Length);

if (allProperties || ContainsRule(Rules.AvailableSpace, ruleNames))

{

       BrokenRules.AssertRequired(Rules.AvailableSpace, _availableSpace);

       BrokenRules.AssertWithOperator(Rules.AvailableSpace, BrokenRules.Operator.LessThanOrEquals, 50, _availableSpace);

 

Now

As you can see this is all very old-style but it has never been the source of problems so I can conclude that it works pretty ok. However, now I want to bring things back into line with the pure Csla way of doing things and I have a few questions:

 

1.       How can I achieve dependency rules where one property needs the value of another to check its validity? When I override AddBusinessRule I cannot do something like:

BusinessRules.AddRule(new MaxLength(NameProperty, AvailableSpace.Length));

                I know about the Dependency but this doesn’t help me link the values of both. It only helps me ensure both are checked. Do I have to create more specific rule implementations that know about both properties? Hope not! I expect I have lots and lots of rules where properties need values of others to determine validity.

 

2.       I also have a concept of dealing with LazyLoaded properties in a different manner because I don’t want these to signal invalidity if they haven’t actually been loaded. There is, of course, a trade-off here but the concept is around rich object graphs whereby an assumption is made that if the parent (for want of a better word) has a valid identifier for the child then it can be assumed the child is valid without actually loading that child and checking the IsValid property. The theory comes from the idea that if the child was saved in the first place it must have passed its own validity checks. So the default behaviour for these is to check just that the Identifier value of the child is present (which, for simplicity, you could think of as an identifier field on a database). Once that child has been loaded then it’s own IsBroken will be called to ensure it is entirely valid.

 

3.       I hope that was clear because it lead me to the next question: I have always run a rules check when an object is loaded because I never want to trust the state of the underlying data. However, as I mostly deal with grids of data this can be significant overhead especially in circumstances where only my business layer will affect that data (I never trust this premise either btw!). What is the recommendation in this area – check the rules on load or not? I assume a built-in check on save? Or perhaps it is only on set which would leave me exposed if I don’t check on load.

 

4.       On the fly rules…one of the great things about Assert is that it is easy to build weird rules very quickly. Silly example (ignoring error checking) might be:

BrokenRules.Assert(Rules.Name, string.Format("{0} should not start with a lowercase letter", Rules.Name), BrokenRules.Severity.Warning, Char.IsLower(_name[0]));

Do I seriously have to create new rule classes for all of these kinds of rules? I have seen the Lambda rule but no sample of it in use. Wonder if it helps here?

 

5.       Finally, had anybody thought about how to use the old-style Assert method to somehow create, attach and apply rules to an object. I’m thinking here about refactoring old code without actually changing it.

 

Sorry if this is too long to read – I expect it is but I find it easier to give as much info up front rather than drip feed.

I just hope the formatting stays intact!! On that note surely with all the combined skills here we could create a better site for this forum. I specifically have problems with 1. formatting and 2. searching - I'd like a far more superior search experience with richer criteria and chained to tree-style results because searching through the results of a query is mess with all the re:xxx, re:xxx, re:xxx entries.

RockfordLhotka replied on Monday, October 24, 2011

jh72i

I just hope the formatting stays intact!! On that note surely with all the combined skills here we could create a better site for this forum. I specifically have problems with 1. formatting and 2. searching - I'd like a far more superior search experience with richer criteria and chained to tree-style results because searching through the results of a query is mess with all the re:xxx, re:xxx, re:xxx entries.

Yes, this forum software has gotten long in the tooth - which is to say that it has not aged well, and increasingly has issues with various browsers. Worse, a number of the admin features have just quit working (who knows why)...  Because I have a free license, I have no support, and I suspect Telligent is on hard times, because they aren't responsive in any way at all.

So I'd welcome the idea of new forum software - probably something more like stackoverflow.com - I just wish I had time to write it :(

xAvailx replied on Tuesday, October 25, 2011

>> So I'd welcome the idea of new forum software - probably something more like stackoverflow.com - I just wish I had time to write it :( <<

Why not just use StackOverflow? Or more specifically create a stackexchange site?

RockfordLhotka replied on Monday, October 24, 2011

The most comprehensive information on this topic is the Using CSLA 4 ebook series.

1) A rule can declare that it needs the values of other properties by setting its input properties in the rule's constructor. The property info fields can be hardcoded in the constructor, or passed as parameters. The rule engine makes sure that the input property values are available to the rule when Execute is invoked.

2) There are a couple threads on lazy loading of objects and how that interacts with rules. The simplest thing is to put such rules (or at least a gateway rule) in your actual class, so the rule has access to the field manager - that way the rule can easily find out if the field has been loaded.

3) You can suppress rule execution, and then run all the rules manually. This is primarily used for web and service interfaces, where all properties of the object are loaded from postback data, and then the rules are run - that is more efficient. You could probably use rule suppression for loading from the database in the same way.

4) Yes, you do need to create a rule. The Lambda rule is designed to make creating super-simple rules easier.

5) This is why we created the Lambda rule - to make it easier to rewrite old-style delegate rules without the effort of creating a class. The same idea should apply to the older-old-style assert rules.

JonnyBee replied on Monday, October 24, 2011

Rocky has already answered most. From Csla 4.2 there are some added functions that will help you:

1. Rules will now consider both InputProperties and AffectedProperties as dependencies.

So you could have a rule like this:

  public class DependencyFrom : BusinessRule
  {
    /// <summary>
    /// When any of the dependencyProperties i changed recheck rules for PrimaryProperty
    /// </summary>
    public DependencyFrom(Csla.Core.IPropertyInfo primaryProperty, params Csla.Core.IPropertyInfo[] dependencyProperties)
      : base(primaryProperty)
    {
      if (InputProperties == null) 
        InputProperties = new List<IPropertyInfo>();
      InputProperties.AddRange(dependencyProperties);
    }
 
    /// <summary>
    /// can add bidirectional
    /// </summary>
    public DependencyFrom(Csla.Core.IPropertyInfo primaryProperty, Csla.Core.IPropertyInfo dependencyProperty,  bool isBiDirectional)
      : base(primaryProperty)
    {
      if (InputProperties == null)
        InputProperties = new List<IPropertyInfo>();
      InputProperties.Add(dependencyProperty);
      if (isBiDirectional) 
        AffectedProperties.Add(dependencyProperty);
    }
  }

3. In data access code I'd rather prefer to use BypassPropertyChecks - ie: your BO will not check Authorization rules (for write) or BusinessRules.

4. We have added several extension methods for Lambda rules, so you can now f.ex add like this:

      BusinessRules.AddRule<Root>(Num1Property, o => o.Num1 > 3, "{0} must be larger than 3");
      BusinessRules.AddRule<Root>(Num2Property, o => o.Num2 > Num1, () => Resources.Num2LargerThanNum1, RuleSeverity.Warning);
      BusinessRules.AddRule(new Dependency(Num1Property, Num2Property));

  We have NOT created overloads that accepts input/affected properties to create dependencies - but you can extend these further for your own use.

5.  Not focused on the old style of Assert, but I have been working on an article for adding the 3.8.x rule methods in 4.2 with lambda rules to support existing code. Article and code is not quite ready yet for publishing. :-(

For samples/intro on rules look at the \Samples\Net\cs\RuleTutorial project in 4.2 Samples download. Still work in progress but does show the possibilities with the new rule engine.

jh72i replied on Tuesday, October 25, 2011

Fantastic. Thank you both so much for 1. taking the time to read that tome and 2. providing such comprehensive answers. I still have things to figure out but with a comfort level now that my issues are addressed.

There is no doubt that the content of this forum is excellent so hopefully someone has a suggestion for a worthy presentation!

jh72i replied on Tuesday, October 25, 2011

Spent a wee while looking at this now including the BusinessRuleDemo project. Please don't take this the wrong way but my feedback would be that I really feel you have taken a beautifully simple concept (Assert) and replaced it with something overly complex and less usable. 

I'm new to it so am certainly speaking without being fully equipped but I expect many people have or will have struggled with this. And I feel I'm writing code that I really don't want to write - my goal of concise, simple yet powerful business object classes looks like its going to take a knock!

jh72i replied on Tuesday, October 25, 2011

[quote]

Answered (Not Verified) RockfordLhotka replied on Fri, Jul 9 2010 2:14 AM
Suggested by RockfordLhotka

The Lambda rule is really an experiment for implementing the most trivial of custom rules - I'm not entirely sure it is terribly useful to be honest.

For anything beyond the most trivial of rules, I create actual rule types.

The thing about Lambda is that it doesn't achieve any testability, so it doesn't really seem all that great to me for anything beyond super-trivial one-liner rules.

Rocky, for migration of a large base of code I cannot see any achiveable way other than replacing Asserts with the Lambda & Dependency rules (nice work on those extensions Jonny). Can you illustrate/explain some of those testabilty issues?

JonnyBee replied on Tuesday, October 25, 2011

Hi,

I  have another post in progress for unit testing of business rules. If you're interested in the code then send private message.

I am considering to add unit testing as separate project to RuleTutorial. (Read: I want to propose another way of testing the rule implementation rather than creating integration tests in the samples).

The RuleTutorial is simple console apps to illustrate how the DataBinding interfaces can be used and I also addded Console.WriteLine inside some of the rules to illustrate the context in which the rules run. That would not be so visible with unit tests.

Ex. from TransformationRules:

.... Rule ToUpper running from CheckRules
DataPortal_Create finished
.... Rule ToUpper running from Name was changed
.... Rule CollapseSpace running from Name was changed
set value: "RoCkY        lHoTkA" and actual is "ROCKY LHOTKA"

About to set Num1,  old value: 0
.... Rule CalcSum running from affected property or input property
Num1 set to: 8 and Sum is 8

About to set Num2, old value: 0
.... Rule CalcSum running from affected property or input property
set Num2: 14 and Sum is 22

Press <ENTER> to continue.

 

 

RockfordLhotka replied on Tuesday, October 25, 2011

jh72i

Spent a wee while looking at this now including the BusinessRuleDemo project. Please don't take this the wrong way but my feedback would be that I really feel you have taken a beautifully simple concept (Assert) and replaced it with something overly complex and less usable. 

There are problems with Assert.

Obviously you can overcome these issues, and people wrote what amounts to their own rules engine to make this all work.

 

jh72i replied on Wednesday, October 26, 2011

RockfordLhotka

There are problems with Assert.

Obviously you can overcome these issues, and people wrote what amounts to their own rules engine to make this all work.

I suppose that's my struggle: I had overcome those last 3 issues so many years ago with my own simple IsBroken() and SetProperty(,,,,, rule1, rule2) that I'm struggling now emotionally dumping that in favour of the new Csla way. I had always accumulated rules in one place/method in each business class but cannot remember now if that if you did that in pure Csla.

With the first I'm still a little in the dark. What is hard about testing the business rules? Ok, again, I'm a little unfamiliar with exactly how Csla does things these days but my own tests might look something like: 

var name = "New User";
var newObj = User.Create(new User.CriteriaCreate(name));
Assert.IsNotNull(newObj);             // ** check that it was actually created
Assert.AreEqual(name, newObj.Name);// ** and created with the right name

var breakingValue = "name that is too long";
newObj.Name = breakingValue;
Assert.AreEqual<string>(breakingValue, newObj.Name); // ** check that the bad name was set
Assert.IsFalse(newObj.IsValid); // ** check that the object is invalid. It should be!

 // alternative: individual rule check
Assert.IsFalse(newObj.IsBroken(User.Rules.Name));

// for illustration: this would fail but kickout the broken rules also
Assert.IsTrue(newObj.IsValid, newObj.BrokenRulesString); // ** check that the object is valid

There is, of course, the concept of exposing the Rules associated with the class but in reality these are simply shortcut strings referring to the property names. I can easily call newObj.IsBroken("Name"). I chose not to use PropertyInfo at the time because of the overhead and lack of simplicity.

Anyway, in pure Clsa (old versions) could you not just Assert IsValid, set a property, then Assert IsValid again. Is that too simplistic?

jh72i replied on Wednesday, October 26, 2011

Just to confirm is this how I should be testing for rule breaks in Csla 4.x: 

var br = newObj.BrokenRulesCollection.GetFirstBrokenRule(UserSetting.ItemKeyProperty);

Assert.IsNull(br, br.Description);

 

tmg4340 replied on Wednesday, October 26, 2011

I believe the testing issue Rocky refers to is testing the rule logic itself.  Under the old system, you're testing your business rules within the context of the BO itself.  This means you have to create a BO, and deal with all the other attendant code that goes along with that, just to make sure your business rule is functioning properly.  Making rules "first class citizens" - i.e. objects in and of themselves - means you can test the rule in isolation and confirm that it works regardless of the BO it's used in.  Then, if you have a BO where the rule isn't functioning properly, but you have rule tests that say it does, you know the problem is in your BO and not the rule.  Conversely, it also allows for testing the BO without potentially having the rule logic pollute your tests.

I admit it can be a subtle difference, but it's an important one from a unit-test perspective.

HTH

- Scott

jh72i replied on Wednesday, October 26, 2011

tmg4340
I believe the testing issue Rocky refers to is testing the rule logic itself

Thanks guys for the replies. You know what I had actually understood the concept of testing the rule itself in isolation but didn't mention it. Was going to but as the conversation was in the context of the Assert approach I decided not to confuse things. I have always created common rules (as alluded to before) so testing of these in isolation is obviously a very good idea. But, of course, their usage often give them the context they need to be actually useful so testing the rules associated with a business object in various states is a goal too.

I was particularly interested in why the Assert mechanism is so "hard/impossible to unit test".

Of course I don't want to drag anybody off on a tangent of why this or that wasn't done this or that way. The decisions have been made and, the thing is, the quality of Csla gives me confidence in those decisions - that they are the right road for Csla. Doesn't necessarily mean I won't pursue what I perceive to be a simpler approaches throughout the framework if I can maintain the integrity of the code at the same time.

Of course also it would be remiss not to provide feedback to Rocky from the front line. Sometimes we design and code something that we later realize isn't ideal. Assert obviously being one example albeit I still harbour feelings that it was a great approach.

Thanks again for the feedback and the code snippets. In terms of testing the Validity of a business object I wonder if you could tell me the approach you use - how you test a specific property/ruleSleep relationship?

 

 

RockfordLhotka replied on Wednesday, October 26, 2011

Personally I often test rules within the context of the object - just like you have been doing previously.

That is problematic though, for two reasons:

  1. More test-focused developers dislike that, because it is an integration test, and I wanted to ensure true unit testing was possible
  2. Properties that have a lot of rules, especially if you add in async rules, are extremely hard to test like this - it is almost always easier to test the rule independent from the property

 

jh72i replied on Thursday, October 27, 2011

Thanks for this Rocky - that I might be a Csla dinosaur but my ways are not totally extinct yet is good to know.

And, oh yeah, async rules - another area I am looking forward to uncovering :) 

JonnyBee replied on Wednesday, October 26, 2011

In the pure sense of Unit Testing you'll want to test the actual rule and that class only - rather than an integration test that involves the actual BO and all linked rules.

Take into consideration:

  1. Validation Rules - that only set errror/warn/info.
  2. Transformation rules that changes the value.
  3. Lookup rules that set properties (and trigger new run of validation rules)
  4. Async vs Sync rules.
  5. more ...

So as an example for testing a Lookup Rule:

 

    [TestClass()]
    public class LookupCustomerTest : BusinessRuleTestBase
    {
        private Root _myBO;
 
        [TestInitialize]
        public void InitTests()
        {
            _myBO = Activator.CreateInstance<Root>();
            var rule = new LookupCustomer(Root.CustomerIdProperty, Root.NameProperty);
            InitializeTest(rule, _myBO);
        }
 
        [TestMethod]
        public void Rule_MustBeSync()
        {
            Assert.IsFalse(Rule.IsAsync);
        }
 
        [TestMethod]
        public void Rule_MustHaveCustomerIdAsPrimaryProperty()
        {
            Assert.IsNotNull(Rule.PrimaryProperty);
            Assert.AreEqual(Root.CustomerIdProperty, Rule.PrimaryProperty);
        }
 
 
        [TestMethod]
        public void Rule_MustHaveInputProperties_CustomerNumber()
        {
            Assert.IsTrue(Rule.InputProperties.Contains(Root.CustomerIdProperty));
        }
 
        [TestMethod]
        public void Rule_MustHaveAffectedProperties_Name()
        {
            Assert.IsTrue(Rule.AffectedProperties.Contains(Root.NameProperty));
        }
 
        [TestMethod]
        public void Execute_MustSetOutputProperty()
        {
            // load values into BO
            LoadProperty(_myBO, Root.CustomerIdProperty, 21164);
            
            ExecuteRule();   // will add values into InputPropertyValues in RuleContext
 
            Assert.IsTrue(RuleContext.OutputPropertyValues.ContainsKey(Root.NameProperty));
            Assert.AreEqual("Name (21164)", RuleContext.OutputPropertyValues[Root.NameProperty]);
        }
 
        [TestMethod]
        public void Execute_MustSetOutputProperty2()
        {
            // run rule with supplied InputProperties 
            ExecuteRule(new Dictionary<IPropertyInfo, object>() { { Root.CustomerIdProperty, 21164 }});
            
            Assert.IsTrue(RuleContext.OutputPropertyValues.ContainsKey(Root.NameProperty));
            Assert.AreEqual("Name (21164)", RuleContext.OutputPropertyValues[Root.NameProperty]);
 
            // in the samme manner I  could also test for
            //Assert.IsTrue(
            //    RuleContext.Results.Any(p => p.PrimaryProperty == Root.NameProperty && p.Severity == RuleSeverity.Error));
        }
     }

Copyright (c) Marimer LLC