Sanity Check - Highly Configurable App

Sanity Check - Highly Configurable App

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


SonOfPirate posted on Wednesday, October 04, 2006

We are developing an application that has become highly configurable (for a variety of reasons) and I need a sanity check on our thought process.

The application is a simulator.  The UI allows the user to select from a list of pre-defined models, enter test specifications then view the results.  The "rules" governing the specifications are configurable.  For instance, one of the spec's is Displacement.  In the app's config file, we have a setting that defines the acceptable Min and Max values as well as the default value for the field.

The question is where best to put this information?  I see one of two approaches:

1. The Min and Max values for each specification basically define a validation rule.  So, we have already created a ValueBetweenRuleArgs class which accepts the min and max values and validates against them.  For each spec property in our "model" class, we would add a ValueBetween validation rule using the configured min and max values.  Then, when the object is validated, the property would pass or fail the test and the issue reported back to the UI following the same lines as other validation rules.  (This is my preferred route because of its consistency).

2. Use the configuration information to drive the UI.  In other words, either use a validator control (web app - btw) or, since we are using Infragistics' WebNumericEdit control, set the max and min values of the control to the configured values.  The initial value of the control would be set to the configured default.

As I mentioned, I like the first approach better but am struggling with the interaction between the object and the configuration information, for one, and applying the default on object creation (which kind of relates back to the first issue).

Everywhere else that we have implemented custom configuration settings, they are decoupled from the objects they relate to.  In other words, the object doesn't know about or depend upon the configuration information - the values are set by a "third-party".  For example, we have an application that supports the concept of policies to control the application's behavior.  The application's SecurityManager class "reads" the configuration information and create a new Policy object for each settings in the config file and adds it to the application's Policies collection).  Neither the Policy nor PolicyCollection class know anything about the fact that they are being instantiated based on configuration information.  Our goal would be to decouple our specifications in the same manner.

However, I see an issue having a "third-party" object setting business rules for our "model" object - one of which is that our ValidationRules property is protected.  Is this something that belongs in an object factory or is it appropriate for the object to be directly coupled with the configuration settings?

One of the reasons for this, btw, is that the min, max and default values apply to that specification for all "models" - so it's not configurable per model.  This is probably something where ther per-type validation rules in CSLA 2.1 will come in handy, but right now I'm more concerned with following the right approach rather than worrying about per-instance vs. per-type.

Your thoughts?

 

RockfordLhotka replied on Wednesday, October 04, 2006

With your min/max example, it seems to me that this min/max rule is an object in its own right.

Conceptually all business rules are technically objects btw. The fact that we implement them with delegates in .NET is for convenience - but logically you should think of a rule as an object!

If you think of your min/max rule as an object, then it has its own responsibility, and behaviors to support that responsibility. Your business object is collaborating with this rule object to validate the property value.

Since the rule is now truly behavioral, you can consider that loading its min/max values becomes part of its behavior - necessary to implement its responsibility.

Does that help?

SonOfPirate replied on Wednesday, October 04, 2006

Yea, kind of...

I agree completely about a rule being an object, which is why it made sense to implement it using our ValueBetweenRuleArgs class & delegate.  I like the fact that the necessary validation behavior is decoupled from the UI and the approach remains consistent with the way we handle it elsewhere.  My question, I guess, is in regards to the fact that these "rules" are defined outside of our business objects.

I am thinking that you either touched on this in the book or a previous post - the idea that the rules could come from a database or some other source rather than having to be hard-coded into the BO.  If this is the case, though, aren't we tasking the BO with responsibility for loading those rules?

Let's say, for example, that my BO has the following three properties:

Displacement
Compression Ratio
Drive Ratio

In our configuration file, we would have the following settings:

<displacement min="0.1" max="16" default="2.5">
<compressionRatio min="6" max="20" default="10">
<driveRatio min="0.1" max="20" default="2.5">

(I'll spare you the rigors of how this is done...)

When a new BO is created, these properties should be initially set to the values of the "default" attribute in the appropriate element and when validated, the values of the "min" and "max" attributes define the limits that should be imposed.

Should the BO be designed so that the constructor is (something like):

public MyObject()
{
    _displacement = Configuration.Displacement.Default;
    _compressionRatio = Configuration.CompressionRatio.Default;
    _driveRatio = Configuration.CompressionRatio.Default;
}

Then we'd have:

protected override void AddBusinessRules()
{
    ValidationRules.Add(CommonRules.ValueBetween,
        new ValueBetweenRuleArgs("Displacement",
            Configuration.Displacement.Max,
            Configuration.Displacement.Min)
    ValidationRules.Add(CommonRules.ValueBetween,
        new ValueBetweenRuleArgs("CompressionRatio",
            Configuration.CompressionRatio.Max,
            Configuration.CompressionRatio.Min)
    ValidationRules.Add(CommonRules.ValueBetween,
        new ValueBetweenRuleArgs("DriveRatio",
            Configuration.DriveRatio.Max,
            Configuration.DriveRatio.Min)
}

But in doing this, we've added the responsibility for obtaining the values from the configuration file (or any other source) to our object.

Would it be better to have the configuration file parsed and the validation rules set by another object whose primary role is doing this?

Part of what bothers me with this design is that I've broken encapsulation a bit by making the class dependant upon the configuration settings (and classes).  For instance, if the <displacement> tag was missing from the configuration file, where do the defaults come from?  I could setup the properties in the config class as Nullable so that I can check HasValue before using it, but the code just keeps getting more complex.

On the other hand, maybe this is exactly the right approach.  That's why I referred to this as a sanity check.

(What about the idea of having an external Validator class that contains a list of business rules by type then accepts the object in its Validate(object) method?  Then we've encapsulated everything to do with validation in another object.  Bad idea?  I'm assuming so since this wasn't the approach taken for CSLA.)

hurcane replied on Wednesday, October 04, 2006

SonOfPirate:

Should the BO be designed so that the constructor is (something like):

public MyObject()
{
    _displacement = Configuration.Displacement.Default;
    _compressionRatio = Configuration.CompressionRatio.Default;
    _driveRatio = Configuration.CompressionRatio.Default;
}

Then we'd have:

protected override void AddBusinessRules()
{
    ValidationRules.Add(CommonRules.ValueBetween,
        new ValueBetweenRuleArgs("Displacement",
            Configuration.Displacement.Max,
            Configuration.Displacement.Min)
    ValidationRules.Add(CommonRules.ValueBetween,
        new ValueBetweenRuleArgs("CompressionRatio",
            Configuration.CompressionRatio.Max,
            Configuration.CompressionRatio.Min)
    ValidationRules.Add(CommonRules.ValueBetween,
        new ValueBetweenRuleArgs("DriveRatio",
            Configuration.DriveRatio.Max,
            Configuration.DriveRatio.Min)
}



My opinion, which doesn't count for much...

Storing these values in a configuration file is an implementation detail that you may want to abstract out of your primary business object (MyObject). What happens if you decide in the future to store the configuration values in a database, or they need to be retrieved from an external service? With the configuration file logic in the primary business object, you'll have to edit the primary business object. If you have this similar scenario in many business objects, you will be facing a monumental task if you change the location of these min/max constraints.

Based on what I know from your description, I might consider creating a separate class that has the logic that reads from the configuration file. The class would probably have generic methods to retrieve a max value, a default value, and a min value. Each of these objects would take an argument so it knows whether it should load the DriveRatio, CompressionRatio, or Displacement. Your calls to the configuration file would be replaced by calls to the new object.

This makes more code for you to write today, but it will be a huge blessing when you elect to no longer store the defaults in a configuration file. If you are extremely confident you will always use a configuration file to store the values, then the approach you have described will be fine.

If your constraints for MyObject are unlikely to change, I might introduce a business object that has 9 properties for the values instead of having parameterized functions. This would make the code even easier to read.

This isn't much different than choosing to use a DAL for your data access.

SonOfPirate replied on Thursday, October 05, 2006

I'm right there with you in regards to the possibility that the settings may exist in a config file now but move later - to a database, for instance.  In fact, we have a project on the drawing board where we make this an option during installation for clients with higher security requirements.  That is exactly where my concerns were coming from, so thanks for articulating it better than I was.

It is/was my opinion that our BO shouldn't care or be dependant on where the information comes from but how to accomplish this has been giving me fits.  Ironically, I am back on the forum looking to see if anyone had anything because yet again, I am back to addressing this issue.

The thought that occurred to me earlier was an attempt to look at this from a behavioral perspective.  And, again, I come back to the two possibilities of how these limits (we'll ignore defaults for now) are "enforced":

1. If used as a business rule, we would validate the value entered by the user and report a broken rule back to the UI.

2. Or, if tied to the UI, we can limit the possible values right on the UI.

So, I guess I'm kinda asking a couple of questions.

We already make use of methods like CanAddObject to enable/disable controls on the UI, doesn't it make sense to be able to make use of data entry "constraints" in the same fashion?  I realize that we can show a message indicating that "the displacement must be between 0.1 and 16" after the user tries to submit a value of 27 but, thinking as a user, wouldn't it be much nicer to save the user the trouble and not even allow that value in the first place?  And, when dealing with a web app, to enforce the validation rules we must incur a round-trip postback whereas setting the MaxValue/MinValue properties on our data entry field allows these contraints to be enforced on the UI.

That aside, getting back to how to create the validation rules from the config file or other external source brings me back to my original dilemma of how best to handle establishing these rules from "outside" our business object.

hurcane mentioned creating a separate class that handles this.  I am thinking that you are not talking about externalizing the ValidationRulesCollection but having another class that sits in the middle of our BO's and whatever source we are using for configuration data.  Even then, if the location of the settings changes, we have to make a code change - but, we would only have to change it there and none of our BO's would have to change.  Am I on the same page?

I'm still trying to picture how this would work.  We would need to translate the information in this new object into validation rules for our BO.  So, we would add this into the AddValidationRules method (?) to query the new object for any "constraints" that apply to that BO, create the rule and add it to the BO's ValidationRules collection...  And, by doing this, we've maintained encapsulation and kept the ValidationRules property protected...

Then, it would be up to our new object to "pull" the information from whatever source applies.

I think I'm getting it, but I'm still wishing we could make the UI more responsive.

Thoughts?

 

ajj3085 replied on Thursday, October 05, 2006

It seems to me that there is a business object which will handle reading the configuration options.  All your classes which need configuration information should then be able to use this class.  If one class cannot handle all the different options, it sounds like you many need many classes for each kind of option. 

Your other BOs could then translate from these objects to figure out how to build its business rules.

JHurrell replied on Thursday, October 05, 2006

SonOfPirate:

I think I'm getting it, but I'm still wishing we could make the UI more responsive.
 


Pirate,

I haven't had a chance to really dive into 2.1, but I do know that there is a new ToString() inplementation in ValidationRules that returns a list of the rules and their values. With is, you should be able to drive the extents of controls in the UI.

Check out the change log for 2.1 and look for "Csla - Validation Rules (getting a list of rules)".

- John


Copyright (c) Marimer LLC