While I know that it is not perfect, and I know that OOP form validation does not quite sit right with me just yet, I have gone and implemented my first shot at it. First, I upgraded my ErrorCollection.cfc so that it could accept random messages, not just messages that were based on errors found by the service objects. This has to do with the fact that form validation doesn't take place solely inside the service objects but also in the controller that is creating a certain user experience.
Once the ErrorCollection.cfc was updated, I added two methods to my ExerciseService.cfc:
Both these methods do contextual validation based on the action implied by the function; ValidateDelete() validates for exercise deletion and ValidateSave() validates for exercise persistence. Both function return an instance of the ErrorCollection.cfc. Both functions also take an optional instance of the ErrorCollection.cfc. If one is passed in, this is the one that is populated and returned. If one is not passed in, then each method will create its own instance of it, populate it, and return it. This allows one error object to be passed around to multiple service objects as well as the controller layer.
Once the validation methods were in place, I then added internal calls to them from the Save() and Delete() functions within the ExerciseService.cfc. Each of these database-interactions methods check for validation prior to the CFQuery tag. If any errors are found, the function is returned out of, returning a boolean false to indicate that the method was not successful:
- Before we save the exercise information, let's double check
- to make sure that it is valid.
- <cfif THIS.ValidateSave( ARGUMENTS.Exercise ).HasErrors()>
- There were errors to exit this method and return false
- to indicate that the save was not successful.
- <cfreturn false />
The programmer might invoke the ValidateSave() and ValidateDelete() methods from the controller layer as well - it is a public function. At first, I was concerned that this would lead to a redundant system that violated the DRY principle (don't repeat yourself), but then German Buela calmed my nerves. German pointed out that DRY was more about code rather than execution; meaning, while I might have the Validate methods called in a redundant way, both calls were still to the same encapsulated code chunk, which cut down hugely on repetition. Business logic was not duplicated, only calls to it were. This way of thinking made me more comfortable with the concept.
Based on advice from others and due to my inexperience with gracefully handling SQL exceptions, I did not move ahead with the idea of catching SQL errors in the ErrorCollection.cfc. Instead, I now just catch the SQL error, rollback the current transaction (I added ColdFusion CFTransaction tags), and then reThrow the error. This way, the application itself will have to handle any SQL exceptions.
Once the entire service layer was updated, I moved onto the controller layer of my ColdFusion application. I turned the REQUEST.Errors object from a ColdFusion array into an instance of the ErrorCollection.cfc ColdFusion component (which was returned by the ServiceFactory.cfc). Once the data was set into the Exercise.cfc and the validation was performed, I then set the error messages based on the error flags set by the service layer. Right now, there are only two possible error flags that need to be agreed upon bu both the service layer (creator of) and the controller layer (consumer of):
- NameExistence - Flag for no value being set.
- NameLength - Flag for set value being too long.
I like the idea of this hand-shaking on error flags, but at the same time, I don't like that it ties the display to the service layer - at least, a bit more than I like. I think, though, that this is unavoidable. This kind of coupling is always going to happen. Think about the MaxLength attribute of a text field. This is coupled to the property of the data allowed in the database. If the DB and the service layer suddenly decreased the value of a given field, I would have to go back and change all the input references as well as the error message handling. I think this is something that is unavoidable, so as much as I am not happy about the coupling, I will accept that there is some neccessity to it.
Uncomfortable or not, I think the key take away here is the fact that the data will never be corrupted. Even if the controller layer doesn't handle the errors well, the service layer will never allow corrupt data to be entered.
I think the weirdest part of this conversion for me to handle was that fact that the error messages where moved to after the whole Exercise.cfc bean interaction. I am used to having all of my form validation done before the data in the form is used for anything; now, the form data is injected into the Exercise.cfc before any form data value validation is performed. This makes me slightly uneasy because it means that all data TYPE validation has to be performed properly before there is any Exercise.cfc interaction otherwise we run the risk of setting invalid data types into the ColdFusion component.
Ok, so now that my object oriented form data validation is in effect, I am feeling like this Object Oriented ColdFusion Application is in a pretty good place. I am not sure what steps to take next. I know that some optimization could be done, but mostly this would include reducing calls to the ServiceFactory.cfc's GetService() method.
You're definitely making some good progress on your OOP. You may want to look at incorporating some DAO methodology. It helps keep your service objects more organized since move all your queries to a data access object/gateway (single and multiple records respectively).
As far as validation goes, the one thing that I noticed when starting out was that, since they requirements often change, it can start to be a hassle hard coding your validation. I wrote a validation object that you can pass your bean to that reads all the get methods and validates against an xml data schema. Basically you store all your regex and bean mappings in a central xml file. Then when you go to validate you call your validation service and pass the bean and mapping. It supports validating beans inside of beans, arrays, queries, structures, etc. It also provides multiple error messages per field. If you're interested I can send it on over.
Also consider Contextual Validation, as discussed by Fowler: http://martinfowler.com/bliki/ContextualValidation.html
I am very interested in this concept. It sounds quite cool. I would love to get some more information on how this is performed and what exactly it is doing.
Fowler's link is pretty good. I feel like that it kind of the direction I am going in. My ValidateSave() and ValidateDelete() are contextually driven validation methods like his example, isValidForCheckIn()... I think.
Toss me an email. I'll forward you a copy along with an example.
I've noticed that your ErrorCollection CFC treats errors like messages, or strings. Are you concerned about passing an error message directly from your validation logic to your user interface? You're asking service and domain objects to come up with friendly messages for the user interface. That breaks the very useful separation of model and view.
Also, I've noticed a funny behavior with your bad-ass Kinky File Explorer. When you scroll down in a source code file, and then select a new file from the left-hand menu, the viewer scrolls down a ways in the new file.
As far as the File Explorer goes, I am gonna get around to fixing that. It just a matter of putting a "back to top" event when some changes the file contents.
As for the messaging, I am not sure I full understand what you mean. The Model / Service layer throws an exception and gives it a key code, such as NameLength. Then, the controller takes that an creates a user friend message based on that error flag. The Service / domain object isn't really setting the message, just the exception type. It can add a "hint" as to why the exception is there, but that is not intended to be the user-presented message.
My bad! I totally misunderstood your excellent code. That hint property should prove most useful for debugging, I imagine. Rock on!
While I'm annoying you anyway, have you ever messed around with custom exception handling in ColdFusion? You can throw and catch exceptions with dot-separated names like "mysite.errors.FileNotFound". You can even throw and catch Java exceptions (subclasses of java.lang.Exception). That blew my mind!
Sorry, all, if that's a little off topic for this post! :)
The file explorer should now jump back to the top :)
I like to think there is no "off topic" here... it's all about the learning, and that's never off topic. The whole thing behind the Hint is that I am not 100% comfortable with the de-coupling of the two layers, so the hint is my way of dealing with that. I thought of it like the error Message / Detail in the CFCatch object, where one shows a smaller message and then one shows a longer, more detailed message (like you said, for debugging).
I have not dealt much with custom exception throwing because I rarely ever catch specific errors. To date, I have really only dealt with catching any error and dealing with them all the same way.