In CSLA .NET 4.0 I plan to make some fairly major changes to the business rules subsystem. This will include breaking changes, but I think they are necessary and good breaking changes. Here’s the bugtracker issue for the change
http://www.lhotka.net/cslabugs/edit_bug.aspx?id=623
At a high level my goals are
As you can imagine, these changes will have at least some impact on existing rule methods and AddBusinessRules() methods. I hope that the changes are pretty mechanical (and thus easy to make), but this will improve readability and open up some new scenarios that are hard/impossible today.
My current thinking includes a few changes:
You can imagine AddBusinessRules() looking more like this:
protected override void AddBusinessRules()
{
base.AddBusinessRules();
BusinessRules.AddRule(MyRule, MyProperty);
BusinessRules.AddRule(MyRule, MyOtherProperty);
}
And you can imagine a rule looking like this:
private static void MyRule<T>(T target, RuleArgs e) where T : MyType
{
if (some condition is met)
return;
else
{
e.Description = "oops";
e.Result = false;
}
}
or a mutator rule like this:
private static void MyRule<T>(T target, RuleArgs e) where T : MyType
{
e.RuleContext.PropertyValues[e.PropertyName] =
e.RuleContext.PropertyValues[e.PropertyName].ToString().ToUpper();
}
The only trick with that is it requires creating an object instance. The rule method can't be static, it has to be an instance method, and CSLA will need to create the instance.
Using a singleton scheme would probably be best - but will cause support headaches, because developers aren't generally used to coding singletons.
Of course you can do this today. You could implement a singleton object that inherits from ObjectFactory and exposes rule methods. So maybe I don't really need to do any work, since the feature already exists :)
RockfordLhotka:The only trick with that is it requires creating an object instance. The rule method can't be static, it has to be an instance method, and CSLA will need to create the instance.
Rocky,
One of my key concerns with mutator rules is that setting the Property of the BO causes all of the rules to run again. There were various suggestions to get around this issue including short circuiting and using rule priorities. But it would nice if the framework handled all of this for the developer so that they could concentrate on the rules themselves without being concerned about how many times they get executed.
1. Will your proposed changes take this into account?
2. Will mutator rules always run before Validation rules? (Why validate something that you are about to change?)
Joe
This is all good input - which is what I'd hoped for when starting the thread :)
Andy and Joe, I don't have answers for whether my proposed changes will do these things. They weren't on my list, and I'm glad you are bringing them up.
One thing that is on my list is to make sync/async and validation/mutation rules all look and work the same. So making mutator rules run first would require making them NOT look the same so CSLA could detect them and default them to a lower priority or something.
An earlier post also suggested (basically) merging all rules from multiple (dependent) properties into a consolidated list, sorting them by priority and then running them as a single list - thus merging all short-circuiting from all properties into a single run. I think this would be an unworkable mess - but I'm interested to hear what others think about the idea.
While I think that making validation and mutation rules "all look and work the same" is perfectly fine (and also much wanted), I think you can't process them the same, for many of the reasons already listed. As Joe mentioned, there's little point in running a validation rule on a property that's going to be mutated later. And I also see value in Andy's (and others') suggestion to allow for specific scenarios where sets of rules are run, or allow for certain sets of rules to be run programmatically. Creating a rule type would also allow you to relatively-easily insert some latch functionality into the mutator processing, thus satisfying Joe's request.
Perhaps this is something that could be shoehorned into the rule-domain concept. Rule domains are essentially a way to group rules, so you could essentially pre-create two domains. Then you can keep much of the semantics of rule construction identical. Perhaps you weren't considering the concept of nested domains, but it seems to me to be a logical extension that wouldn't add a ton of overhead.
I also see a lot of value in rxelizondo's suggestion of matching up rules with types. I've been bitten by that bug a few times...
HTH
- Scott
Let’s not forget the sync/async issue too.
At least today, sync rules run synchronously, according to
priority. Async rules (only validation today) are then launched at the end and finish
when they finish. There’s no sense of priority for async rules, because they
run concurrently and asynchronously.
I plan to enable async mutator rules, which will STILL run at
the end and will finish when they finish. A mutator rule will be able to change
multiple properties (it will return a dictionary of changed values). But since
it is async, its completion is indeterminate – we don’t know when it will get
done.
That adds a very interesting wrinkle into this whole discussion –
as you all ask for more control, the async world is providing (arguably) less
control.
Async mutator rules sound like a problem waiting to happen.
What happens when two async rules change the same property to different values? Which rule "wins" in that case?
Couldn't both the "run only on property changed" and "this is a mutator" rule be handled by adding a parameter to the AddRule call, indicating that this should should only run on property changed or that it's a mutator?
ajj3085:Couldn't both the "run only on property changed" and "this is a mutator" rule be handled by adding a parameter to the AddRule call, indicating that this should should only run on property changed or that it's a mutator?
Which ever mutator ends last would win. It is true that multiple async mutator rules would be problematic.
But there are two features that I think will offset the issue:
Rule chaining means that one rule can create multiple brokenrule entries and/or can call other rules. The primary motivation here is to allow creation of a rule that invokes an external rule engine that creates numerous broken/unbroken rule results.
Post-processing callback means that you can provide a delegate/lambda that is invoked when a rule is complete. This will occur on the UI thread (or so goes my plan) so you can do any post-processing you'd like to do.
Combine post-processing with chaining and you should be able to invoke a set of syncrhonous rules upon completion of an async rule.
RockfordLhotka:Which ever mutator ends last would win. It is true that multiple async mutator rules would be problematic.But there are two features that I think will offset the issue:
- Rule chaining
- Post-processing callback
Rule chaining means that one rule can create multiple brokenrule entries and/or can call other rules. The primary motivation here is to allow creation of a rule that invokes an external rule engine that creates numerous broken/unbroken rule results.
Post-processing callback means that you can provide a delegate/lambda that is invoked when a rule is complete. This will occur on the UI thread (or so goes my plan) so you can do any post-processing you'd like to do.
Combine post-processing with chaining and you should be able to invoke a set of syncrhonous rules upon completion of an async rule.
Given that "multiple async mutator rules would be problematic", this would seem to be another point in favor of separating the two types of rules. This way, when your SL app asynchronously launches the business-rule validation process, the server-side code can process the mutation rules first - and synchronously - before launching any validation rules. This also feeds into the "don't validate before mutating" concept as well. The way I read it, neither rule chaining or post-processing callbacks solve this issue. But maybe I'm missing something.
- Scott
RockfordLhotka:An earlier post also suggested (basically) merging all rules from multiple (dependent) properties into a consolidated list, sorting them by priority and then running them as a single list - thus merging all short-circuiting from all properties into a single run. I think this would be an unworkable mess - but I'm interested to hear what others think about the idea.
mbblum:This is a "nice to have", but to me a lower priority to be in CSLA than the other items. I have coded brute force walk of the BrokenRulesCollection down the object hierarchy to get a list for display.RockfordLhotka:An earlier post also suggested (basically) merging all rules from multiple (dependent) properties into a consolidated list, sorting them by priority and then running them as a single list - thus merging all short-circuiting from all properties into a single run. I think this would be an unworkable mess - but I'm interested to hear what others think about the idea.
I think you misunderstand the question.
The earlier post suggests a change to the way rules are EXECUTED, so rules from dependent properties are merged into a single ordered list, then executed. So a rules for properties A, B and C might run all intermixed, and a short-circuit in property B would stop rules for A and C.
----
There is a backlog item for a consolidated brokenrules collection feature. That's an entirely different thing, and I may add that - but I agree that it is a relatively low priority since you can do this today with little effort.
The sort order idea is interesting though.
mbblum:...
Here is another "wish list" item. Business Rules that run on the common DataPortal activities, and can be specified to run before or after there execution. As I look at this, it can also be viewed as a request to make DataPortal_OnDataPortalInvoke() and DataPortal_OnDataPortalInvokeComplete() available via Business Rules....
Since you're going to be rework this anyway..
Could there be a way where:
1. Rules run just like they do now
2. Rules ONLY which run in a response to a Property changed (SetProperty).
#2 seems most useful the the "mutation" style rules, which alter values. While you may want to validate data as you read it from your database, these rules I would think wouldn't need to be run. In the past, I've been bitten by this; a Password property which encypted via a rule method would attempt to reencrypt when the instance was loaded from the database. There were other validation rules that I did have to run, so I had to hack together a solution.
I've hit a few other similar scenarios like this as well.
Async rules exist on .NET and SL. And they aren't just server-side rules. They are just async rules - could run on client or server.
Today async rules are validation only - there's no model by which the rule can change the object. My plan is to follow the lead of WF (more or less) and allow the rule to build a Dictionary<PropertyInfo, object> and once the rule is done and processing is on the UI thread, CSLA will update the properties with the values in that dictionary. The result is a safe mutator rule that does its work on a background thread or a server or whatever.
What I hear you saying Scott, is that I shouldn't do this. Shouldn't enable async business rules at all.
But I don't think that's an entirely valid solution, because today you can't do long-running calculations on a background thread, and you can't do database lookups (user enters field A, we go find the value for field B) on SL, etc.
I really think there's a need for async business rules.
I wasn't trying to say we shouldn't have async rules. I know that async rules are necessary (i.e. pretty much the only way to do things) in SL, and they have real benefits in traditional client/server situations. I don't work in SL, but I've found async rulse to be a pretty slick feature in my WPF stuff.
I just think that if we're going to create rules that specifically change property values, then there should be some sort of mechanism to control the execution of those rules, so that the validation rules are ensured of working with "valid" property data - i.e. data that has already gone through any mutation rules. As Joe mentioned in a previous post, why validate data that's going to change after the validation rules run?
Yes, maybe the change is nothing more than an upper-case rule - but maybe it's not. Maybe it's changing "1M" to "1,000,000", and there's a validation rule that checks to make sure the data is numeric (since we've essentially converted a numeric field in the database to a textual field in the UI.) If I can't guarantee that the mutator rule has run, then my validation rule has to be able to deal with non-mutated data, and I've got basically the same code in two places.
I know this kind of thing can, and maybe should, be handled differently (i.e. maybe this is handled in a property-change event instead), so perhaps this is a bad example. But if you're going to allow the concept of mutator rules, then a part of me says that this kind of property-change code should be put there - after all, the ability to enter "1M" is a business rule, isn't it? And if it's in a mutator business rule, then I only have to write the code once, instead of in each object where I need to accept those values.
HTH
- Scott
I do not know if I am worrying about nothing here but the idea of re-running validation rules after mutator rules bothers me. It becomes very easy to get lost in the stack of rules that is running multiple times because the underlying Property value keeps changing due to the mutator rules.
For example say I have 10 validation rules and 4 mutator rules. The property changes in the UI and I expect 14 rules to run. But if the 4 mutator rules run at the end then the validation rules will end up running 50 times and the first mutator rule will run 5 times, the second 4 times, the third 3 times and the fourth 2 times. (Assuming that a mutator rule causes the set of rules to be re-run right away.)
The simplest idea is to always run the 4 mutator rules first and then run the 10 validation rules once afterward.
Now if Rocky can devise a way to set the value of a Property in a mutator rule without causing the rules to be re-run then perhaps the re-running of rules is not such a big deal. I can envision running the 14 rules and then re-running all of them because a mutator rule has run. A second run of rules maybe isn't too bad - I just don't want to see multiple runs. After all some of these rules hit the database and are expensive!
Joe
I don't see anything that suggests the rules would be more than once, unless you somehow set it up to do that. I doubt he's planning to rerun rules when a property is changed in response to a mutator business rule.
ajj3085:I don't see anything that suggests the rules would be more than once, unless you somehow set it up to do that. I doubt he's planning to rerun rules when a property is changed in response to a mutator business rule.
Well, he may not be planning it but that is the way the current system works. Which is why I am concerned about it.
Joe
Right, I should add that I use priorities to ensure that mutator rules always run before validation type rules as well.
Of course, I haven't dived into async rules, so perhaps that's where we're encountering different expereiences, but again my understanding is that right now there is no way to do a mutator rule asyncly. I can see this being a problem with async mutator rules, but it sounds like Rocky believes rule chaining will take care of this problem.
ajj3085:I can see this being a problem with async mutator rules, but it sounds like Rocky believes rule chaining will take care of this problem.
Well, Rocky believes that someone who knows what they are doing will be able to use rule chaining to implement their scenario.
I don't think there's any magic here - async and/or multi-threaded software is always more complex.
But just because async business rules will allow people to screw up doesn't mean I shouldn't enable the scenario. When used carefully and knowledgably, the results should be pretty cool :)
Sorry, I don't mean to be putting words in your mouth. We're all just trying to work out what the changes mean I think.
I agree that the async rules should be an option... it seems pretty likely someone will want to do just this, and if its not there will be stuck.
Today it depends on whether you are writing a private or public rule.
Private rules can easily use BypassPropertyChecks, LoadProperty or a private backing field.
Public rules don't have that luxury unless you build them as instance rules in an ObjectFactory subclass.
I don't plan to change any of that directly.
But as I've said, rules will be able to create a dictionary of output values. That dictionary will be used by CSLA to update the properties specified in the dictionary. Presumably this will be done by calling LoadProperty, but will happen before the post-processing callback.
In other words, the post-processing callback will run after the property values are updated, allowing you to trigger or explicitly execute validation rules on the mutated value(s).
So what will that triggering look like? Right now its ValidatoinRules.CheckRules.. which will run everything again, or passing a property name will run just for that property, but may run everything for that property again.
Is there going to be something like a RegisterRule method, and each rule will have a token of some kind (like the PropertyInfo for properties) so that we can selectively run rules, or is this where the "rule domain" concept fits in?
RockfordLhotka:Today it depends on whether you are writing a private or public rule.
Private rules can easily use BypassPropertyChecks, LoadProperty or a private backing field.
Public rules don't have that luxury unless you build them as instance rules in an ObjectFactory subclass.
This is the point I was making about the re-running of rules. I stated it explicitly in the original thread which the bug ID refers to. When I centralize a rule I have to reflect against the BO and set a Public Property which causes the rules to re-run. A private rule inside a BO can use any of the other techniques to set the backing field and avoid the Property Set.
Joe
JoeFallon1:This is the point I was making about the re-running of rules. I stated it explicitly in the original thread which the bug ID refers to. When I centralize a rule I have to reflect against the BO and set a Public Property which causes the rules to re-run. A private rule inside a BO can use any of the other techniques to set the backing field and avoid the Property Set.
And I don't plan to change this behavior at all, other than maybe creating a base class like ObjectFactory for building rule classes. If I do that then there'd be a clear way to build public rule methods so they can use BypassPropertyChecks and/or LoadProperty() as necessary.
RockfordLhotka:other than maybe creating a base class like ObjectFactory for building rule classes.
Rocky,
making changes mechanical is good
if there automateable with Search and Replace using a helper tool or Regular Expressions that would be even better, as my CSLA application has 250K lines of code
(just signed up to listen to you talk in Boston, and am looking forward to hearing about what's in my future in regards to CSLA -- thanks for the great development framework...this is my second opportunity to see you live, but unlike Vermont, this time around, I won't be riding the Big Yellow Bike from Norwalk there...it's too cold)
Des Nolan
I think there's a logic flaw with the idea that "mutator rules should run first".
GIGO: garbage in, garbage out
Simple mutator rules like a ToUpper might need to run before validation. But complex mutator rules like look up the customer details would only run after you've validated the CustomerId value (or whatever).
In other words, the thought that you can always run mutator rules without running validator rules is flawed. I could see a rule set like this:
You'd want short-circuiting before doing 4. You'd also want to make sure 2 runs before 3.
ajj3085:What is your thinking on the "run all rules" vs. "run only validation rules (such as just before a dataportal_fetch completes) suggestion? Would it be possible to do that one with your currently planned implemeation?
You'll note this wasn't in my original scope. I'm entertaining the idea because it has been brought up, but I'm struggling with how it would work in practice.
If you step back and think about this, what CSLA provides is a primitive linear workflow for the rules attached to any given property. Once side-effect of the CSLA 4 changes is that you really could use a WF 4 workflow per property for your rules (or so I hope).
Separating business from validation rules implies two workflows that run one after another. But as I just pointed out, that ignores the very real scenario where business and validation rules are intermixed, and interdependent.
So do you really think it is realistic to just run validation rules, when some mutators are required for validation to work? Or to just run business rules, when you don't know if the data is valid?
If you don't trust the data in your database (hence you want to run validation rules in DP_Fetch), why do you trust its format? Don't you want to run at least your basic mutators on it too?
My thinking was that, using an attribute or something similar, the rule list would still be a single list, and as csla is running through it, it would know (because you asked it to) to simply not run the ones not indicated as validation.
Its a good point that some mutators are required for validators to work, or maybe the other way around, but I think its still important to enable this scenario as well, and I already have a few places where, in the DP_F I have to set a flag, call CheckRules, then disable the flag. There's another case where it got even more complicated than that, where I had to run all the rules again in certain cases, but there was a mutator rule I couldn't run, so I don't think its always the case that you trust your data, but not its format.
Here's the scenarios I have:
1. There's a concept of a product bundle; its just a grouping of products into a unit and assigned a part number. Users can choose to have the bundle calculate its pricing, or set fixed pricing. Going back and forth should calculate or restore the fixed pricing (if it was fixed to begin with). The way it works is that its not good enough to simply store the original value on fetch, this was causing unexpected behavior. So I have logic that runs when the pricing type changes, which asks each price to calculate or "uncalculate" which deteremines the apprpriate value to use depending on how the object's state when first loaded. On each load, I can't have that business rule running, and it should only run in response to a property changed for the price type.
2. Consider the use case of an "edit account" type scenario. You may have rules on some properties, some which may only be warnings, which you want to run and should run right out of the database. But being a good programmer, you create a salt value and hash the password when the user types a new password into the text box. This ensures the clear-text password never travels over the network. But again in the fetch, attempting to run all rules will rehash the already hashed password fetched from the database, unless some hackery is put into place.
Maybe these scenarios aren't worth the effort, but I know I'd find it very useful. I think there is precedent for this too, as Wpf allows seperate validation vs. property changed methods when you create a DependencyProperty.
I think a more general solution may be in order.
One of the changes that is part of my core requirements is to alter the method signature for rule methods. It will basically be something along this line:
void RuleMethod(RuleArgs e)
But RuleArgs will now be substantially more sophisticated than it is today, providing a relatively broad set of input and output capabilities.
One thing it could contain is some userstate object that you provide to CheckRules(). This would allow you to use the userstate to do anything you wanted really, in terms of deciding whether a rule should run or not. It would enable your scenario, my scenario and other scenarios - much more flexible than if CSLA hard-codes some specfic scheme and thus precludes the others.
Copyright (c) Marimer LLC