I’ve been reading a few questions and answers regarding exceptions and their use. Seems to be a strong opinion that exceptions should be raised only for exception, unhandled cases. So that lead me to wondering how validation works with business objects.
Lets say I have a business object with getters/setters for the properties on the object. Let’s say I need to validate that the value is between 10 and 20. This is a business rule so it belongs in my business object. So that seems to imply to me that the validation code goes in my setter. Now I have my UI databound to the properties of the data object. The user enters 5, so the rule needs to fail and the user is not allowed to move out of the textbox. . The UI is databound to the property so the setter is going to be called, rule checked and failed. If I raised an exception from my business object to say the rule failed, the UI would pick that up. But that seems to go against the preferred usage for exceptions. Given that it’s a setter, you aren’t really going to have a ‘result’ for the setter. If I set another flag on the object then that would imply the UI has to check that flag after each UI interaction.
So how should the validation work?
Edit: I've probably used an over-simplified example here. Something like the range check above could be handled easily by the UI but what if the valdation was more complicated, e.g. the business object calculates a number based on the input and if that calculated number is out of range it should be recjected. This is more complicated logic that should not be in th UI.
There is also the consideration of further data entered based on a field already entered. e.g.I have to enter an item on the order to get certain informaion like stock on hand, current cost, etc. The user may require this information to make decisions on further entry (liek how many units to order) or it may be required in order for further validation to be done. Should a user be able to enter other fields if the item isn't valid? What would be the point?
I tend to believe business objects should throw exceptions when passed values that violate its business rules. It however seems that winforms 2.0 data binding architecture assumes the opposite and so most people are rail-roaded into supporting this architecture.
I agree with shabbyrobe's last answer that business objects should be built to be usable and to work correctly in multiple environments and not just the winforms environment, e.g., the business object could be used in a SOA type web service, a command line interface, asp.net, etc. The object should behave correctly and protect itself from invalid data in all these cases.
An aspect that is often overlooked is also what happens in managing the collaborations between objects in 1-1, 1-n or n-n relationships, should these also accept the addition of invalid collaborators and just maintain a invalid state flag which should be checked or should it actively refuse to add invalid collaborations. I have to admit that I'm heavily influenced by the Streamlined Object Modeling (SOM) approach of Jill Nicola et al. But what else is logical.
The next thing is how to work with windows forms. I'm looking at creating a UI wrapper for the business objects for these scenarios.
Have you considered raising an event in the setter if the data is invalid? That would avoid the problem of throwing an exception and would eliminate the need to explicitly check the object for an "invalid" flag. You could even pass an argument indicating which field failed validation, to make it more reusable.
The handler for the event should be able to take care of putting focus back onto the appropriate control if needed, and it could contain any code needed to notify the user of the error. Also, you could simply decline to hook up the event handler and be free to ignore the validation failure if needed.
Exceptions should not be thrown as a normal part of validation. Validation invoked from within business objects is a last line of defense, and should only happen if the UI fails to check something. As such they can be treated like any other runtime exception.
Note that here's a difference between defining validation rules and applying them. You might want to define (ie code or annotate) your business rules in your business logic layer but invoke them from the UI so that they can handled in a manner appropriate to that particular UI. The manner of handling will vary for different UI's, eg form based web-apps vs ajax web-apps. Exception-on-set validation offers very limited options for handling.
Many applications duplicate their validation rules, such as in javascript, domain object constraints and database constraints. Ideally this information will only be defined once, but implementing this can be challenge and requires lateral thinking.
Assuming that you have separate validation and persist (i.e. save to database) code, I would do the following:
The UI should perform validation. Don't throw exceptions here. You can alert the user to errors and prevent the record from being saved.
Your database save code should throw invalid argument exceptions for bad data. It makes sense to do it here, since you cannot proceed with the database write at this point. Ideally this should never happen since the UI should prevent the user from saving, but you still need it to ensure database consistency. Also you might be calling this code from something other than the UI (e.g. batch updates) where there is no UI data validation.
I'd definitely advocate both client and server-side validation (or validating at the various layers). This is especially important when communicating across physical tiers or processes, as the cost of throw exceptions becomes increasingly expensive. Also, the further down the chain you wait for validation, the more time is wasted.
As to use Exceptions or not for data validation. I think it's ok to use exception in process (though still not preferrable), but outside of process, call a method to validate the business object (eg before saving) and have the method return the success of the operation along with any validation errors. Errors arent' exceptional.
Microsoft throw exceptions from business objects when validation fails. At least, that's how the Enterprise Library's Validation Application Block works.
Perhaps you should look at having both client-side and server-side validation. If anything slips past the client-side validation you can then feel free to throw an exception if your business object would be made invalid.
One approach I've used was to apply custom attributes to business object properties, which described the validation rules. e.g.:
The attributes can then be processed and used to automatically create both client-side and server-side validation methods, to avoid the problem of duplicating business logic.