Part 1: Validation of Business Objects

In my previous post I mentioned that I believe that validation logic should live in the business layer

rather then in the UI.  I went as far as to say you should have the rules for validation defined in one place, your business layer, and have your other layers use those rules to apply the validation as necessary.  I’ve decided to write a series of posts on how I’ve implemented such a setup in my custom .net framework (I need a cool name for it like “Rails” or something ).  In this post I’m going to quickly show how I define validation rules in my business layer and talk through how I then use those rules within the UI.  In future posts I’ll dig into more specifics of what the code actually looks like to apply the rules in the UI.  Let’s take a look at a sample entity object:

public class Role : EntityObject {
   public Role() : base() { }
   public Role(int id) : base(id) { }

   [Persist, Required, MaxLength(50)]
   public string Name {
      get { return _name; }
      set {
          SetDirty(true);
          _name = value;
      }
   }

   [Persist, MaxLength(250)]
   public string Description {
      get { return _description; }
      set {
         SetDirty(true);
         _description = value;
      }
   }

   [Persist, Required]
   public int SiteID {
      get { return _siteID; }
      set {
        SetDirty(true);
        _siteID = value;
     }
   }

   string _name = String.Empty;
   string _description = String.Empty;
   int _siteID = -1;
}

As you can see this is a simple Role entity that contains a Name, Descriptions, and SiteId.  Within my framework I define many of the rules for how an object should be treated using a set of custom attributes.  The [Persist] attribute tells the underlying framework that when .Save() is called on a role instance that particular property should be persisted to the backend data store. 

From a validation perspective we can see two custom attributes that define some validation rules for the Role object.  The [Required] attribute specifies what properties require a value to be set, and the [MaxLength] attribute defines the maximum length for the name and description property.

Every time a Save() is called on a role object the persistence layer will ensure that a Name, and SiteID is provided and that the lengths of the Name and Description properties are shorter then 50 and 250 characters.

The second piece of the equation is getting the rules defined via these custom attributes applied when people are entering roles into the EditRoles.aspx page.  The easiest solution would be to drop a couple RequiredFieldValidator controls on the page as well as set a maximum length on the textboxes that users enter the name and description into.  But what happens when my rules change?  What happens when I decide that description should also be required, or when I adjust the name field to be 100 characters rather then 50.

If the validation rules where defined in both the business layer and UI layer I’d have more then one place to update my validation rules.  Those of you familiar with the DRY principle know that this is far from ideal.

Instead what I’ve done in my framework is applied the validation rules defined on my business object to my UI.  One of the wonderful things about ASP.NET is that you can add and remove controls from pages at runtime.  What this allows me to do is dynamically inject required field validators into my ASP.NET page at runtime by inspecting the rules defined on my Role entity.  This keeps the rules defined in one place and helps me to sleep better at night knowing that nobody is going to get a Role into my system without a name.  What a relief!

# re: Part 1: Validation of Business Objects

Sunday, March 05, 2006 10:34 AM by Bil Simser    
Interesting approach and I like it. It does mean more plumbing work to handle the custom attributes and some kind of base page to interrogate the objects and create the controls but looks like a good approach.

I guess I've always stuck to the OOTB mentality and not built custom features. Okay, attributes are far from custom and I'm talking about things like using intrinsic types, so maybe I'm comparing apples to oranges.

My classes have done the validations in the setters (usually) and would throw exceptions or add information to an ErrorHandlerStack class if invalid. The attribute approach is much more elegant.

I'm assuming you do things like handle scenarios where there's an attribute missing so if one day my field is required but the next it isn't.

The only question is how do you handle setting a value directly? Say if I did this:

Role role = new Role();
role.Name = "SomeStringMoreThan50Characters";

That voilates the business rule and the setter allows it. So do you modify the setter to read the attribute and act accordingly or is there something in the EntityObject class that handles this?

Looking forward to seeing the next steps.

# re: Part 1: Validation of Business Objects

Sunday, March 05, 2006 5:16 PM by Steve    
It definitely means a little more work in developing the base framework to support it but in the end it's worth it.

Regarding the question about setting the value directly I'm not sure you're going to like my answer. I just let it happen. The way I look at it is as a user of the Role class I'm not going to want to want the Role class to throw me exceptions every time I set a value. I'd rather tell it everything I'd like to do to it and then ask if it's valid.

So I don't actually perform the validation as values are set, just when someone asks the Role if it's valid, or when someone tries to save it. It might not be ideal for everyone in every scenario but it's worked well for me thus far.

# re: Part 1: Validation of Business Objects

Sunday, March 05, 2006 6:01 PM by Bil Simser    
Okay, I didn't like your answer ;)

I prefer to keep my domain objects valid at all times so if I'm working in a system that has no persistance (as I am when I'm building something via TDD) I always know that my domain objects are valid (after all, why would I have business rules if I'm not going to abide by them myself).

I see your point of checking if it's valid (either in the UI or the database) and using attributes will make that easy. I just have an uncomfortable knot knowing that my Role object would be invalid until I checked it. Not sure if I would follow that approach but looking forward to seeing more posts on this.

Thanks!

# re: Part 1: Validation of Business Objects

Sunday, March 05, 2006 9:39 PM by Steve    
Haha, I had a feeling you wouldn't. The way I see is it doesn't particularly matter if I get notified when I set the property or when I actually save the object as long as the validation rules are enforced before the entity gets into the system.

I do understand the desire to keep entities in a valid state at all times though so perhaps there's still hope for me ;-)

# re: Part 1: Validation of Business Objects

Sunday, March 05, 2006 10:50 PM by David Hayden    
The reason for adding validation to the UI is to provide the user immediate feedback that the input is invalid without requiring a postback, for example, in the case of ASP.NET. Sure you save some effort on the development side by not having to add and maintain the UI validation, but the user may end up having a negative experience because of it. I am not sure 37Signals would agree with you here :), but I certainly understand where you are coming from. I thought I read a post from your past where you were generating UI validation from the attributes on your business objects? Has that changed?

I also understand Bil's point about keeping the object valid at all times. If you wait and throw the exception later during a save, it makes it difficult to debug. The best practice is to throw the exception immediately during the set so the problem is obvious, but I don't always follow that rule either :) It isn't inconceivable to throw an invalidoperationexception if everything is not properly set as it should be, although the idea sounds really ugly. A number of classes in the .NET Framework do this.

I am more curious how you deal with the overhead of reflection using the attributes to determine how an object is persisted to the underlying datastore. I assume you generate your CRUD dynamic sql statements using those attributes? Do you cache those statements after you build them at runtime? I would love to see some code as to how you handle that, but I apologize for digressing from your series of posts :)

Great post, Steve.

...Dave...

# re: Part 1: Validation of Business Objects

Sunday, March 05, 2006 11:43 PM by Steve    
I am 100% with you. I'm all about providing the user with immediate feedback within the UI regarding validation logic. When I say I want it defined in one place that doesn't mean it's only applied in that one place. The rules should be defined in one place and then made available and used by many other components...such as the ASP.NET page that users are entering data into.

I believe in having validation in the UI. I believe in providing the user with immediate feedback regarding validation. I like UI validation and I use it. I just approach it from a slightly different angle then most.

I'll try and touch on the overhead of reflection as part of my series of posts since I can already see the series might morph into more then just validation.

Thanks for reading :)
Cheers,
Steve

Post a Comment

 
 
Prove you're not a spammer: 
7 + 6 =