I continue to work on validation rules 2.1 - lots of changes here - all for the better I hope, and all preserving backward compat as well.
I still haven't decided on the error/warning/info thing yet, but I think I'll enhance BrokenRulesCollection to do the work here. And yes Xal, I did decide to add Information as a severity type - so all three will be there.
However, another new feature is short-circuiting, and I'd like some API input on this.
Short-circuiting is the idea that when CheckRules() hits a broken rule for a given property, no remaining rule methods for that property are invoked. Coupled with another new feature, priorities, this means you can prioritize expensive rules to run later, and short-circuit on earlier (low cost) rules.
When you call AddRule() or the new AddSharedRule(), you can optionally specify a priority for the rule. Larger numbers are processed after smaller numbers, with the default being 0 (the highest priority).
I have three questions on this one, for general input.
First, should it be as simple as I just described (simple is good), or a bit more complex? Specifically, I could allow you to specify a maxPriority value, so all rules up to that priority would be invoked, but if any of those rules were broken then processing would stop at maxPriority. This would allow ALL the low cost rules to run, but none of the high cost if you set it up that way.
My current boolean approach means the first broken rule terminates all rule processing.
Second, what about Warning and Information rules? If they are broken, do they stop processing? I think no - I think only Error severity rules stop processing - but I'd value thoughts here.
Third, there's an API question. Right now I altered CheckRules() to accept an optional boolean where you can specify to do a short-circuit. This is OK, but means I'll have to add a overloads to PropertyHasChanged() as well, because CheckRules(propertyName) is normally not called directly. That seems somewhat ugly, but might be the right answer.
The other alternative is to require that you set ValidationRules.EnableShortCircuting to true if you want short-circuiting. But that'd be a global setting, and I can envision cases where you'd only want to short-circuit specific properties.
Of course along that line, my current scheme of CheckRules(true) is global across all properties too. So maybe the answer is to have you set a property per property in AddBusinessRules():
ValidationRules.AddRule(...)
ValidationRules.EnableShortCircuting(propertyName)
So then you'd define once which properties should short-circuit their rule processing, and you wouldn't need to specify short-circuiting on each CheckRules() or PropertyHasChanged() call.
This sounds really interesting :)
I think I could really use the short circuit functionality in our system. Especially the priority thing that would be awesome!
I don't think that I'm experienced enough to offer any suggestions on how to implement this into the framework but it sure sounds like a really cool idea!
I like the idea of max priority; you’re solving both issues I currently have.
I would use low level rules to check that the object meet db constraints, i.e. field lengths, etc. and can be persisted to the database. High level rules would be used to check more complex business rules; example would be to make sure that a certain sales discount is still valid. If the object can’t meet the low level rules, then there is no point of running high level rules.
Warning and Information, I can’t think of why I would have Information rules. I’m treating warning as, I have met the low level rules, but the object hasn’t been validated against higher level rules. This would inform the user that I can save the object, but I should come back here at soon as possible. I guess you could put the information message when the object is loaded again. Something like, this object was saved with warnings, but I don’t know if you would want to take the processing time upfront again.
I agree, I wouldn’t stop the save process unless they don’t meet the low level rules…aka, can’t persist the object to the database.
On the issue of short circuiting, If a developers short circuits a object and the object breaks at rule check priority 10 (going from 0-100, 60 start of high level rules), then the developers doesn’t get a results of all the low level items that are broken. Meaning that the developer would display to the end-user in a grid, forms, whatever all the items that are broken before the object can be persisted, but say the object breaks again at priority 50. I wouldn’t want to display multiple forms to my end-user saying, oops broke here, oh, and here, etc. So as long as the Short circuit flag doesn’t short circuit all low level items, I’m fine with it.
Thoughts/Ideas?
RockfordLhotka:However, another new feature is short-circuiting, and I'd like some API input on this.
Short-circuiting is the idea that when CheckRules() hits a broken rule for a given property, no remaining rule methods for that property are invoked. Coupled with another new feature, priorities, this means you can prioritize expensive rules to run later, and short-circuit on earlier (low cost) rules.
When you call AddRule() or the new AddSharedRule(), you can optionally specify a priority for the rule. Larger numbers are processed after smaller numbers, with the default being 0 (the highest priority).
I have three questions on this one, for general input.
First, should it be as simple as I just described (simple is good), or a bit more complex? Specifically, I could allow you to specify a maxPriority value, so all rules up to that priority would be invoked, but if any of those rules were broken then processing would stop at maxPriority. This would allow ALL the low cost rules to run, but none of the high cost if you set it up that way.
I prefer the term "shortCircuitThreshold" to "maxPriority".
RockfordLhotka:My current boolean approach means the first broken rule terminates all rule processing.
Yuck! I hate playing 20 questions over a network connection!
Oops, got 1 error, fix and resubmit.
Oops, get 1 more error, fix and resubmit.
Oops, get 1 more error, fix and resubmit.
Oops, got another 1 error, kick the programmer in their ...
RockfordLhotka:Second, what about Warning and Information rules? If they are broken, do they stop processing? I think no - I think only Error severity rules stop processing - but I'd value thoughts here.
I suggest the ability to allow/disallow processing to continue when a warning is reached. From a user perspective, they submit a save and get a warning - so processing is rolled back. They resubmit with a "yeah, I know, do it anyway" indicator and the warning doesn't stop processing - that time only.
Otherwise, the difference between Warning and Information is just "text" and could be handled in the message text.
RockfordLhotka:Third, there's an API question. ...
Sorry, too new to the framework. Just now connecting my read-only business objects to my pages. Haven't gotten to the editable ones yet.
Flexible is nice, but so is the ability to do very little typing to get reasonable levels of rule validation processing... :) Just too new with your framework to have a good feel for exactly where that sweet-spot is.
Thanks for this set of features!
ps. Please seriously consider adding the ability to ask the object for a list of rules. (Not broken rules, just the rules.) What's needed is a unique RuleId, a RuleLabel (which would default to the error message specified for the rule). Any thing else we can easily store in tables, but being able to programmatically access the actual list of rules coded in the object is just too valuable a quality control and user-interface enabler to pass up... :)
david.wendelken:
Yuck! I hate playing 20 questions over a network connection!Oops, got 1 error, fix and resubmit.
Oops, get 1 more error, fix and resubmit.
Oops, get 1 more error, fix and resubmit.
Oops, got another 1 error, kick the programmer in their ...
That is the "hunts the joker" effect i spoke in my previous post.
Fabio.
david.wendelken:I suggest the ability to allow/disallow processing to continue when a warning is reached. From a user perspective, they submit a save and get a warning - so processing is rolled back. They resubmit with a "yeah, I know, do it anyway" indicator and the warning doesn't stop processing - that time only.
Otherwise, the difference between Warning and Information is just "text" and could be handled in the message text.
Rocky,
Adding short-circuiting on rule execution process along with non-instance validation rules would improve performance of CheckRules.
I would vote for processing stop at maxPriority when broken rule occurs. I also like the idea of using property instead of adding new overload to CheckRules method when enabling short circuit. I see this to be easier with code generation. Initially code will be generated with short circuit turned off (of course) where only commonly used validations (which are low cost) are generated. User might add some validation rules, and when the rule is expensive, user may turn on short-circuiting by setting a property in Initialize method.
I would like to ask you to also consider adding rule-dependency feature. Many times I have use case where rule B need NOT be tested when rule A is broken; and rule C need NOT be tested when rule B is broken.
While this is pretty easy to implement with BrokenRules.Assert, I have not found a way to implement this with Rule Manager.
Ideally, I would like to have our Rule Manager handle dependency just like DTS package define the workflow process. I would settle on having single dependency feature in rule manager. Example: you may define dependency in AddBusinessRules:
ValidationRules.AddRule(...,"PropertyName!RuleName") where PropertyName!RuleName is the dependent rule name.
And also use the following in custom rule method to short-circuit or aboard the validation method:
if(BrokenRulesCollection.Contains("PropertyName!RuleName")) return true;
Ricky
rasupit:I would vote for processing stop at maxPriority when broken rule occurs. I also like the idea of using property instead of adding new overload to CheckRules method when enabling short circuit. I see this to be easier with code generation.
rasupit:
I would like to ask you to also consider adding rule-dependency feature. Many times I have use case where rule B need be tested when rule A is broken; and rule C need be tested when rule B is broken.
Fabio,
I said using property is easier does not necessary mean the overload approach is problematic. The overload can be implemented as follow:
add member level variable:
int _maxPriority = 0;
then change create/fetch method in generated class to CheckRules(_maxPriority). When user decided to use short-circuiting, they will initialize _maxPriority to the level they choose.
Because of these changes, any existing or on going application that would like to take advantage of this feature will need to regenerate their generated/base class; Not so using property.
I meant to say "Many times I have use case where rule B need NOT be tested when rule A is broken; and rule C need NOT be tested when rule B is broken."
This is an example how you'd implement rule-dependency in v1.x using BrokenRules.Assert:
bool ruleAIsBroken = .... check for rule a here
BrokenRules.Assert("RuleA", "Rule A is broken", ruleAIsBroken);
if(!ruleAIsBroken) {
bool ruleBIsBroken = .... check for rule b here
BrokenRules.Assert("RuleB", "Rule B is broken", ruleBIsBroken);
if(!ruleBIsBroken)
BrokenRules.Assert("RuleC", "Rule C is broken", .... check for rule c here);
}
Ricky
xal:Just in case, I'm going to ask, because I see that we all make different assumptions on how this is meant to work...
I believe Severity and ShortCircuiting are two different features that have nothing to do with each other...
Is my assumption right Rocky? I believe that was the intention, but after seeing some of these posts I'm getting doubts...
Andrés
Yes, Severity and short-circuit/priority are two different things. They only intersect at one point, and that is whether a non-Error broken rule should cause short-circuiting to occur (i.e. a Warning is enough to short-circuit). At present I plan to only have Error severity short-circuit processing.
Priority and short-circuting are directly linked. Without priority short-circuiting obviously can't work. While some edge cases may exist for priority alone, I'm really adding it to support short-circuiting.
I like the threshold term and the simplicity (both in terms of use/documentation and implementation) of setting the threshold on ValidationRules. I think that should provide enough flexibility to solve the vast majority of scenarios.
Another idea that did come to me while reading the comments (or maybe someone posted this and I sublimated the idea?) is to allow a rule to specifically request a short-circuit by setting e.StopProcessing to true. I think this would be orthogonal to normal short-circuiting by priorty threshold. In other words, setting e.StopProcessing=true would have an absolute and immediate effect - regardless of the Severity or Priority or any other factor.
Then there's the concept of dependencies between rules. I do not intend to build this into the current rule method scheme, at least for 2.1. Partially this is because you can do this yourself with the infrastructure CSLA provides. Remember, rule methods are just methods. If you have a complex set of dependencies based on state, then you can have your rule methods work together to form a state machine or interact to establish a sort of rule-flow or whatever you choose.
Given that the broken rules collection is public, you can have rules check to see if other rules are broken and change their behavor accordingly. Coupled with priorites, you can ensure that dependent rules run after the rules they depend on, so I think the pieces are there to do some pretty complex stuff.
Yet another issue is whether to expose the object's rules through a public method or interface. As I've said before, I'm not adverse to this idea, but it is not clear to me how to expose these in a meaningful way. Yes, I could easily expose the rule names, but that may not capture the details embedded in the custom RuleArgs parameter, which is also important. All that said, I may still consider doing this because the ToString() rendering for RuleMethod and RuleArgs should result in something you could parse in some manner.
What I'll toss out in this regard is this: I could change the way ToString() works in RuleMethod and RuleArgs to generate a "uri" for rules - like
rule://method/property?param=blah
or
rule://method?property=blah¶m=blah
The value to this is that (maybe) the System.Uri class could be used to parse such a thing rather than requiring a custom parser for the current method!property!param scheme.
RockfordLhotka:Yet another issue is whether to expose the object's rules through a public method or interface. As I've said before, I'm not adverse to this idea, but it is not clear to me how to expose these in a meaningful way. Yes, I could easily expose the rule names, but that may not capture the details embedded in the custom RuleArgs parameter, which is also important. All that said, I may still consider doing this because the ToString() rendering for RuleMethod and RuleArgs should result in something you could parse in some manner.
What I'll toss out in this regard is this: I could change the way ToString() works in RuleMethod and RuleArgs to generate a "uri" for rules - like
rule://method/property?param=blah
or
rule://method?property=blah¶m=blah
The value to this is that (maybe) the System.Uri class could be used to parse such a thing rather than requiring a custom parser for the current method!property!param scheme.
That might work pretty well! Feels right, anyway.
Or, for a simpler solution on your part, you could just expose a "ruleHelpText" property that the programmer who wrote the rule was expected to fill in with useful text. That changes the problem of "How do I deduce what the heck I should tell a user in human-understandable terms?" to "Let me show the ruleHelpText property in the list of rules."
Not that I want to discourage you from giving us a programmatically accessible way to know what the rule parameters are... :)
Or, for a simpler solution on your part, you could just expose a "ruleHelpText" property that the programmer who wrote the rule was expected to fill in with useful text. That changes the problem of "How do I deduce what the heck I should tell a user in human-understandable terms?" to "Let me show the ruleHelpText property in the list of rules."
Yes, I think that was my point. Showing this stuff to an actual human could have value as you describe, but then you need to have some way to incorporate arbitrary and unknown RuleArgs parameters into the description text.
Your example, for instance, is only good if the 10+ years is hard-coded. But a good rule method would get that value from a custom RuleArgs so it wouldn't be fixed at 10. To generate that description you'd need to somehow merge the args value into your text.
I don't want to get involved in that game. I went down that road with the rule description attribute in version 1.3 and it was a mistake.
RockfordLhotka:Yes, I think that was my point. Showing this stuff to an actual human could have value as you describe, but then you need to have some way to incorporate arbitrary and unknown RuleArgs parameters into the description text.
Um, not exactly. Not to get a list of business rules enforced by the object!
Complex RuleArgs parameterization of the rule explanation text is not necessary. In fact, it's probably a bad idea. I've been doing rule-based architecture for quite a few years, and the overwhelming number of rules in my experience can be explained with a simple, non-parameterized description. Often, it only takes a simple declarative sentence.
For documentation/ quality-assurance purposes it isn't essential either.
I would be perfectly happy to supply the explanatory sentence when I write the rule. Since I proposed an optional rule definition parameter, other people could be equally happy not supplying one. :)
RockfordLhotka:Your example, for instance, is only good if the 10+ years is hard-coded. But a good rule method would get that value from a custom RuleArgs so it wouldn't be fixed at 10. To generate that description you'd need to somehow merge the args value into your text.
I disagree. The rule explanatory text could simply be written as:
Actual Error: 'Invalid date'
Error description: "If Z-Type has a non-blank value, the End Date must be after Start Date by at least as many years as defined in Policy 135. (Menu option: Policies->List Business Policies)."
Or, even spiffier:
If Z-Type has a non-blank value, the End Date must be after Start Date by at least as many years as defined in <a href="http://PolicyManual?Id=135" target="_policy">Policy 135</a>.
With web services, I might even be able to get it to supply the value at run time.
(But that's for me to learn later this year! Csla, C#, VB, ASP, regular expressions and javascript are keeping me pretty busy at the moment. :))
Um, not exactly. Not to get a list of business rules enforced by the object!
Complex RuleArgs parameterization of the rule explanation text is not necessary. In fact, it's probably a bad idea. I've been doing rule-based architecture for quite a few years, and the overwhelming number of rules in my experience can be explained with a simple, non-parameterized description. Often, it only takes a simple declarative sentence.
For documentation/ quality-assurance purposes it isn't essential either.
I would be perfectly happy to supply the explanatory sentence when I write the rule. Since I proposed an optional rule definition parameter, other people could be equally happy not supplying one. :)
RockfordLhotka:I did decide to add a method to ValidationRules to generate and return a string array of rule descriptions...
Basically, short of hitting her over the head and giving her amnesia, or breaking her fingers so she couldn't type, I can't think of a way to have made it harder.
She had about 50 objects coded in a week and a half, and it took another 2 days to tidy them up once the compiler got installed. Would have taken half that time if we had been able to correct misunderstandings with compiling/testing the first object in a series rather than replicating the same mistake in each follow-on object!
Two weeks after that, I've got about a dozen screens up and running, sweet as you please. If I had already known how the ASP.Net 2.0 controls worked, I could have easily done them in a week. I think after I've built another dozen pages, I could have done 2-4 pages a day, which would have cut the time in half.
The Csla portion was a dream to use!
I saw the PayPal donation button on your site and sent you a $25 thank you. If your publisher is like mine, that's about 8-12 book royalties. :)
Thanks for such a great job, and for sharing it with all of us!
I love the new stuff being discussed for 2.1
For my part I would like to see error/warning/info settings for rules. Errors prevent saving the data whereas warnings and info do not. It would be nice if you could require a supervisor override for warnings, but not for info. (If a clerk is entering data and they get a warning, they could call a supervisor to override by entering a password or something)
I am not too concerned about short-circuiting.
It seems that the order of processing is only important if you also implement short-circuiting. If you are always going to process all of the rules, maybe the order doesn't matter. Whereas if you use short circuiting then you might want to process some sooner than others. But, do you process the most critical ones first, or the ones that the user can most easily fix first? If you process the lowest cost rules first, you may find that there is another rule that the user cannot fix, but will not know about it until they have corrected more trivial conditions, only to find out that they still cannot finish processing the data.
Is there a way to put rules into a table so that rules can be added or changed without re-compiling and distributing the application?
Harvey Sather
Saskatoon, Canada
HarvDotNet:Is there a way to put rules into a table so that rules can be added or changed without re-compiling and distributing the application?
Yes, you can do this. It does require a bit of reflection, because your table or config file needs to contain the assembly name, class name and method name of the rule method. Using that information you can create the appropriate delegate reference to associate with your property.
The tricky part turns out to be dealing with custom RuleArgs objects, because you need to come up with a mechanism by which you can provide appropriate parameter data to your custom args parameter in a meta-data driven manner.
Copyright (c) Marimer LLC