Before I get into the coding, I just wanted to give data validation in object oriented programming a little more thought. I don't want to get caught in an endless loop of analysis, but I think a little up front discussion is a good idea. Starting at the most low level, I want to work my way up through the various types of validation that might be required.
As the most basic level, we have to check that our instance data is of the correct type and that it has certain criteria. For example, if we are expecting an email address, we have to make sure that the value is:
- A string.
- A valid email format (ignoring those people who seem to think that having addresses with "+" in them are important :P).
- Is less than or equal to 70 characters in length due to the database field size.
Normally, I would do some of this data type validation as part of the CFArgument tag on my Set-style functions. However, since we are using generic getters and setters in this application we give up that ability. As such, all of this type of validation needs to be part of some sort of Validate() method, probably located within the Bean itself. What we don't want to do is drive ourselves insane with the extreme repetition of all of these type of validation. There must be a limited number of ways in which this type of validation can be done; we need to find a way to abstract that out and encapsulate it in such a way that it can easily be shared.
Dealing with composed objects is a little more gray. For example, in OOPhoto, a gallery must have at least one photo to be considered valid. Should this be a special validation case? Or is this really just be an Array that has a minimum length of one? Of course, in reality, for a photo gallery to be valid, not only does it have to have at least one photo, but each of those photos has to be valid as well.
Now that we have touched on single-object validation, let's talk about validation that involves the relationship between data points. By this, I mean something like the uniqueness of email addresses or usernames. Let's say that for a User object to be valid, it has to have a unique username within the system. Now, the User object itself has no concept of this - it exists on an island with no concept of other users. Likewise, I know that I have a unique Social Security Number because I am told that is true by a higher authority - not because I have validated that constraint myself. The validation of SSN uniqueness is not MY concern, it's the concern of the entity that ties me to other citizens - the application, if you will, within which we all reside.
To get validation of this nature, all we have to do is put some sort of validation in our service layer. This validate method will know about all the related entities and therefore can handle the relationship-based validation. This is no problem with a top level object, but when it comes to composed objects, it is slightly more complicated. For a composed object to be validated, it will have to be passed up to its own service layer by its parent object. This requires our top level objects to have pointers to the service layer of its composed objects.
Or does it? One way that we can get around this is to encapsulate that redirection behind each object's Validate() method. We could call Validate() on an object and have that turn around and call its own service layer:
<cffunction name="Validate()"> <cfreturn VARIABLES.ServiceObject.Validate( THIS ) /> </cffunction>
This way, we can always call Validate() on an object and be certain that both data type and relational validation rules are going to be applied.
The last layer of validation has nothing to do with objects at all. The last layer of data validation involves form-specific validation. For example, making sure that the provided CAPTCHA text matches the image or making sure that a confirmation password matches the first password. Stuff like this has nothing to do with the data of the application; it is there as part of the user interface experience only. Therefore, we need something in place that handles that outside of the primary objects. I guess, what we need is an object that exists only to validate a given form.
While we're at it, why not have that form-specific object process the form as well. This would go back to what Dan Wilson was referring to in that forms should be "processed" rather than viewed by the Controller as data submissions. This way, we could keep the Controller very short and simple.
Oh my god. Something just occurred to me! All this time, I have been thinking that my validation and processing logic has been in my Controller. But it isn't. My controller just consists of CFSwitch statements! My validation and processing actually happens in my "action" files. A very interesting epiphany. I have definitely been operating under the assumption that I need to shrink my Controller, but that simply isn't true. I am not saying that the above conversation doesn't apply - it still does - but I have to stop hating on my controller so much; she's actually pretty slim already.
Sorry for that digression. Remember, I don't know any of this stuff - you are getting the raw output stream from my head :) I'm learning as I go here.
I think for starters, I will create the validation methods in my object but I will leave the form-specific validation in my action files. I am not sure about these form-specific objects. There's something about them that feels very wrong. My gut is telling me that since they are one-to-one in my application (one object per specific form) there is no need to add the overhead of an object. I might as well just leave that logic in my "action" file.
Ok, the next time we talk, I should have some new code to review.
Want to use code from this post? Check out the license.