My company is developing a GUI application that allows users to query a legacy database system and have the results displayed back to them on the screen (the results just come back in a blob of plain-text). I'm struggling with the best way to structure the interaction between the user interface and the domain layer, especially validation of user input.
Basic Use Case
- User selects a query to run from a menu in the application.
- The application code displays the data entry form for the selected query.
- The user enters the parameters for the query. If a field contains invalid data, it is immediately highlighted in red, and its tooltip text is changed to display an error message (i.e. if you are entering a Person query, and you enter a date of birth in the future, for example, the date of birth field will immediately turn red).
- When the user clicks
Run Query
, the application runs a second validation pass; this second validation pass is required in order to run validation checks that involve multiple fields. If the this validation check passes, and all the fields are valid, the query is sent; otherwise, the user is prompted to fix any remaining errors.
My Current Validation/Error Reporting Strategy
Currently, I'm using domain-centric validation, but the overall design seems messy to me and maybe a little too over-engineered. A brief overview of the current design:
Domain layer: I have one class per query. Every query class contains a collection of IQueryField
objects that hold the values entered by the user. Each query class implements a common IQueryMessage
interface, which defines (among other things) a Validate
method. This method is called to enforce message-level validation rules (i.e. rules that must examine the state of multiple fields at once). The IQueryField
interface also defines a 'Valdate' method (among other things). This is to support per-field validation rules.
Per-field validation: To handle the per-field validation and error reporting, the data entry code binds each input control to an IQueryField
; whenever the user changes the value of a control, it calls the the corresponding IQueryField
's Validate
method, which in turn fills a Notification
object (just a collection of strings at the moment) with any errors detected in the value entered by the user. The user interface code then checks the Notification
object and changes the appearance of the user control to indicate an error condition, if necessary.
Message-level validation: When the user tries to send a query, the application calls the Validate
method on the IQueryMessage
instance associated with the data entry form (at this point, the data binding code has also ensured all the message's fields have been populated from the input controls on the form, and the per-field validation code has been run). If there are any validation errors, the user interface displays them at the top of the form. If there are no errors, the data entry form is closed and the query is serialized and sent over the network.
Is Something Wrong Here?
I feel like something isn't "right" here. I have a few issues with the current design:
I would like the domain-level validation code to indicate the name of any fields that are in error, bur I don't want to hard-code the UI label captions into the domain classes. One possibility I thought of was to have the domain-level
Validate
methods generate messages with a field placeholder, such as"%s cannot be in the future"
, and have the UI code fill in the placeholder with the correct label.The
IQueryMessage
andIQueryField
interfaces both have a method calledValidate
. I'm thinking this should be extracted into a separate interface, (IValidatable
perhaps), but I wonder if I am making things needlessly complex.I'm using VB6, so I can't use inheritance in my classes (VB6 supports classes but not inheritance). I can only define and implement interfaces. Because of this, and because of the way my current interfaces are designed, I'm duplicating a lot of boiler-plate code in my implementation classes. I am thinking of solving this with an inversion-of-control approach. For example, I was thinking of defining a single concrete
QueryField
class, which could be initialized with a collection ofIValidationRule
instances that define what validation rules to use, then theQueryField.Validate()
method would just collect the results of executing each rule. This way, the validation rules can be tailored to each field, but theQueryField
class can handle all the common field-related stuff (field name, field length, required/not required checks, etc.).
How Can I Improve This?
I'm interested in any refactoring suggestions and hints on improving the current design. Also, I'm not necessary tied down to domain-centric validation; other suggestions are welcome. The main motivation behind using domain-centric validation was to keep increase encapsulation, and allow query message and field objects to be used in a non-GUI environment, without having to rewrite all the validation logic.