How do you architect business validation logic without creating duplicate code, but ensuring a positive user experience?
Validation as Business Logic
Mandatory data fields, or duplicate checks are user/business imposed requirements. These types of requirements are best served by the code in the system that reflects the business - the Domain Model. The business 'Domain' is the part of the system that immediately reflects the needs and requirements of the business and other vested interests. The 'Model' or 'Domain Model' is the code that implements these requirements. All business validations must exist in the model or business layer. This is why the model exists, its job is to model the business, and it is the part of the system that is accountable for the business requirements.
In C# I like to separate the model classes from other utility classes with a namespace, say the 'model' namespace. If the model is large, the key classes can be further separated into a 'core' namespace. At the moment my prefered method for communicating validation errors from the model is through exceptions, using the built in .NET framework exceptions and implementing my own custom exceptions (make sure to run FX Cop against your code to ensure your exceptions are CLS compliant).
Validation as Presentation Logic
Exceptions from the model layer passed up to the application screens lead to a very poor user experience. The user is forced to work through each validation one at a time, and the exceptions messages may not be very helpful.
It is not the responsibility of the model layer to solve this problem. The model reflects and ensures the business requirements, not the user requirements. The presentation layer is the user part of the system, if it enforces business requirements, it is merely doing this to improve the user experience. Re-implementing business validation with validation controls or code behind checks is a good way to improve the user experience. The presentation layer understands the user and can provide far better messages and help than the model layer. The presentation layer should wrap the model by either ensuring that the model doesn't return errors, or if that is not possible, by translating errors and retrieving other information about an error to help the user fix the problem.
Validation as Data Logic
Databases also provide tools to enforce business logic. Unique constraints, foreign key constraints, non-null fields, these are all database imposed business validations. It is possible to structure a relational database without these things and make it the responsibility of the application to manage the data, but that is not a practical solution. Many times data must be manipulated 'behind the scenes', usually for technical/performance reasons. The database exists to store the model, and it must be able to do this reliably. Reliablity and integrity is why validations exist in the database.
Validations in all Layers
Although it may be more difficult to manage/change, a good system has business validations all layers.
- Presentation Layer - screens may implement entry validations to provide a rich user experience
- Business Layer - Model objects enforce business rules because that is their job
- Data Layer - Databases enforce business rules to ensure data integrity, which ensures that the data layer will satisfy the demands of the model.
I have seen many strategies that try to move validation logic into a single place. For example, Data driven strategies where a validation can be changed by simply updating a row in a table. The 'Naked Objects' pattern attempts to improve transparency of business validations in a business layer. Some of these strategies create a great deal of complexity and an unpleasant learning curve. Homegrown meta-data systems require maintenance developers to essentially understand the entire system grasp the impact of a change.
In my straight forward model, admittedly, the simple task of making a field mandatory requires updating code in the presentation layer, changing the business model object, and setting not-null on the database table. While this change has broad impact on the system, it is an intuitive change. Each code change is highly isolated from the rest of the application. It is very easy to understand a system that is implemented in this way, and that is very important. Using a well understood validation pattern is acceptable, but creating an abstract/opaque solution reduces maintainability. Nobody likes maintenance work, so make it straightforward. How the change is made must be obvious not necessarily easy. Yes it is easy to change a validation by updating a row in a validation logic table but is it obvious? What is the impact to the system? How do you know it's going to work - talk to the developer that coded it? Not acceptable.
Validations in each of the layers all stem from the same requirement, but have distinct purposes. Trying to combine these purposes is difficult and probably not worth the complexity.