Yesterday, I discussed that as I am learning more and more about Object Oriented Programming (OOP) in ColdFusion, one of the biggest mental hurdles seems to be data validation. Well, data validation and the translation of data errors into user-friendly error messages. I talked about how the translation cannot be made in the Model because that would couple the Model too tightly to the rest of the application when, in fact, the Model is not supposed to have to worry about much more than itself.
This morning, to explore this concept a bit more, I wrote a little demo code that takes a very small form page and two small objects and does all the OOP data validation goodness. The scenario is that we have this Girl.cfc object. She has a FirstName, LastName, and Clothes property. The Clothes property is an array that holds zero or more instances of the Clothing.cfc object (don't worry, on this blog it will always have at least one clothes instance). Both of these objects have a Validate() method that knows how to validate itself and its composed objects.
Now, before I get into it, I thought I would try something a little different. I recording a quick video to demo the code I created. I thought this was good because you really get to see the error object and how it is created in conjunction with the form:
| | | | ||
| | | |||
| | | |
Ok, so let's get down to the nitty griddy. The two Models I have, Girl.cfc and Clothing.cfc, are quite simple. They both extends a Base.cfc that has the generic Getter / Setter functionality and then implement their own Validate() method. Let's take a look at the Base.cfc:
Launch code in new window » Download code as text file »
Not much going on here. The generic Get() and Set() methods can take any kind of data. The only validation that actually occurs at this level is that you can only get or set a property that is explicitly defaulted in the VARIABLES.Instance scope. Nothing too exciting.
Next, let's take a look at the Clothing.cfc:
Launch code in new window » Download code as text file »
As you can see, the Clothing.cfc Model only had two properties: Name and Size. The majority of the ColdFusion component is concerned with validating the data, which is not exactly the smallest amount of code. Since our generic getter / setter methods don't provide any low level type-validation, we have to check not only the property values but also that they are the correct type of data as well.
As the Clothing.cfc runs through its data validation rules, it stores the invalid properties by key in a ColdFusion struct. Each validation error gets some sort of error explanation in the struct, but this is NOT the error message that we want to show the end user. The point here is that we are creating an "index" of invalid properties. If there are no data validation errors in this ColdFusion component, then we simply return an empty struct.
Now, let's take a look at the Girl.cfc. This is slightly more complicated because the Girl.cfc has simple properties as well as an array of composed Clothing.cfc instances:
Launch code in new window » Download code as text file »
Again, not a whole lot going on here, but the composed objects do throw a bit of twist into the validation. Now, not only does the Girl.cfc have to validate its own simple properties, FirstName and LastName, it has to, in-turn, check the validation of each of the Clothing.cfc instances. And, since each of the Clothing.cfc instances may return like-named-errors, we have to come up with a way to create contextual errors. To do this, I have decided that I would suffix the "Clothes" property with the index of the instance the Girl.cfc Model is currently validating.
This nested data validation-flag struct ends up giving us a ColdFusion structure that could look like this:
| | | | ||
| | ![]() | | ||
| | | |
By doing this, we are creating an index of data validation errors that has nothing to do with interfaces in which they are used. The only thing that is required at this point is that the interface or the controller knows that these structs are indexed by properties and sub-context property keys.
So, that's all I have as far as the Domain Model goes - time to dive into the Controller and View pages that created the demo video you watched above. I am trying to keep this as simple as possible such that I can understand the situation more clearly without having to worry about the rest of the framework. Therefore, I am dealing with an extremely small Front Controller that handles only this form:
Launch code in new window » Download code as text file »
Almost nothing going on here - we are either displaying the confirmation page or the form processing page.
Now, the form processing page, act_form.cfm, is where the magic is all coming together. This controller, or action file - to be honest, I am not really sure what to call this - has several steps. Let me briefly outline it:
And, I'm not even taking into account the form population that would need to take place if this was an existing Girl.cfc instance - we are assuming an insert-only situation (Girl objects, insert-only situations... this sounds kind of dirty for a post about OOP, my apologies). This code is a bit complicated:
Launch code in new window » Download code as text file »
The part that is tripping me up is that so much code goes into error validation. I used to feel that when it was all inline, it was still verbose, but seemed much less than this. I feel that with the error message translation, I am almost doing data validation twice, in a way. But, I have to always be keeping the mindset that this is for the long-term benefits of easy maintenance as well as unit testing (when I start doing that); I am front-loading the effort so that changes later can be done much faster and with greater confidence.
Once the action page has finished, it's pretty straight forward from there. The form display page that gets included simply outputs the form fields as well as any error messages that have been passed on from the action page:
Launch code in new window » Download code as text file »
So that's pretty much all there is to it. I am not gonna bother showing you the code for the Confirmation page as it just outputs a confirmation message - no logic or ColdFusion code.
Time for reflection - how do I feel about all of this?? Well, for starters, I think this is the happiest I have been so far with an attempt at Model-driven data validation. I like that the Model objects are smart and know how to validate themselves. And, I really like the "indexed" error struct with nesting options that I came up with; I feel like that's a really clean way to handle the nested validation contexts that arise with composed model objects.
As far as the error message translation? I am not sure how I feel about it. I might want to move that into the View. I might want to move that into a "helper" object that some people discussed in my previous post. I think it might be a little too much for me to think about at this second (plus I have to get back to work); I am gonna let it sink in before I really make a decision.
I would love to get some feedback on this methodology! I am trying so hard to wrap my brain around all this OOP.
Also, what do you guys think of the video? Does it help explain the concept? I am thinking of trying to incorporate video a bit more to help tie it all together.
Download Code Snippet ZIP File
Comments (17) | Post Comment | Ask Ben | Permalink | Other Searches | Print Page
This was great! The screencast really helps me understand what is going on as you move the mouse pointer over the sections! I wish I could have learned more of my CF skills this way. Nice job.
Posted by Adam on May 9, 2008 at 3:33 PM
Whenever you talk about validation in an object oriented world, the question comes up as to whether you should be stuffing potentially invalid data into a bean and then validating it, or validating the data, then stuffing it into the bean. It is a small bit of overhead, but if you validate the data first and determine it is bad, then you don't have to create the bean object.
Either way, a while back when facing this problem, I got tired of writing the same set of validation code over and over .... checking email addresses, valid date ranges, etc. So I created Validat (http://www.alagad.com/go/products-and-projects/validat-data-validation-engine/validat-data-validation-engine), a data validation engine which will take a structure of data, a business object, or virtually any other collection of data and validate that collection of data against a set of rules you have predefined. The result is an error collection which can contain resource bundle keys for the error messages, thus allowing you to customize and set those messages outside of your model.
This is not meant to be an advertisement, just an FYI if you had not heard of Validat to know that a potential solution was available. That and to raise the same question again of validation before the bean is created and stuffed or afterwards. My current preference is before as that keeps the bean simple, but I have gone back and forth.
Posted by Jeff Chastain on May 9, 2008 at 4:28 PM
@Jeff,
No problem on the plug - I am all for anything that helps me to learn this stuff. Are there any articles or blog posts on how to use it. I think I understand what it is trying to do, but I am not sure about the implementation.
The other day, Peter Bell and I were talking about creating some sort of validation object - actually he mentioned that in response to all the questions I was asking him about data validation. We had thought about maybe doing this as part of the Pair Programming demo, but we didn't have nearly enough time.
I can definitely see how so much of is repetative. In fact, I could see how 90% of the validation out there could probably be done using regular expressions:
\w{1,50}
Required field between 1 and 50 characters containing "word" data. That's like the majority of stuff out there. Of course, more complex validation, like database lookups, that would have to be taken care of in other ways. Although, from your description of Validat (on the Alagad site), it looked like you could write custom validation rules that could do some stuff like that.
Can you point me to a good example? Thanks.
Posted by Ben Nadel on May 9, 2008 at 4:54 PM
@Ben,
There are a series of blog postings that were done on the Alagad blog describing the functionality and capabilities of Validat (http://www.alagad.com/go/blog?categoryId=8F6F9CEA-3048-55C9-439427FF4AD5446E). There is also some basic sample code included in the download.
As with most open source projects, it suffers from a good complete set of documentation or examples. If you have any questions, let me know. I would love to hear your feedback.
Posted by Jeff Chastain on May 9, 2008 at 4:58 PM
@Jeff,
Awesome, I will check it out. Thanks.
Posted by Ben Nadel on May 9, 2008 at 5:11 PM
The video was great. Now the next thing to consider is what happens when you have a change in requirements. Another form somewhere else needs to validate the data in a different way. say for different user roles or permissions, some other business rule, or return a different error message. The OO way is to encapsulate that change somehow so you are not modifying that validate function to fit both the first case and the new case every time a change occurs.
CoolJJ
Posted by CoolJJ on May 9, 2008 at 5:29 PM
I generate XML validation files based on database metadata that I can then go in and modify as necessary. So I avoid the work of having to "translate" an error by simply returning a displayable error message as part of the Result object that is returned from the Model. Something like:
<rules custom="false">
<properties>
<property name="age">
<rule message="Age is required." required="true" />
<rule message="You must enter a valid number." type="numeric" />
<rule maxlength="3" message="Your Age cannot be more than 3 characters long." />
</property>
</properties>
</rules>
A validator factory gets the correct XML, parses it, and caches the rules for future use. It will allow for any validation types that IsValid can handle, such as SSN, zip code, etc. I can specify custom="true" and create a custom Validator for that object which will automatically be called and where I can add custom validation that is beyond the scope of the built-in validation. I've found it to be pretty flexible.
Posted by Brian Kotek on May 9, 2008 at 5:33 PM
@CoolJJ
This is where the concept of having a seperate validation engine really starts to look good. With the validation engine, you can have multiple sets of rules for data based upon a given state or any other reason. Then, you can pass the bean or the collection of data to the validation engine, letting it know which set of rules to validate the data against. This way, by keeping the actual validation logic outside of the bean, you keep it encapsulated.
Posted by Jeff Chastain on May 9, 2008 at 5:35 PM
It all looks pretty good, and the implementation of a validation system is honestly the best way to approach it. The only thing I struggle with personally is tieing my server side validation engine and my client side engines together.
I personally am a big fan of not letting data go across the wire until it has been validated as much as possible on the client side. I then validate server side to make double sure.
This allows two things:
1. No needless transmission across the wire from your primary interface until client validated.
2. The server side acts as a double check, or if you are calling these server side methods from something other than your primary interface (i.e. webservice) you have the proper validation still in check.
Posted by Adam Presley on May 9, 2008 at 6:17 PM
I also like having the validation engine separate from the object. For re-use as stated above and with reuse is less memory usage since its not in every bean. I do usually create a validation cfc for each bean and abstract the common utilities away in case I swap the common validation out. Plus this way I could pass a bean (yes I load the bean with bad data also) to the [Bean name]Validation.cfc and then it passes simple values to the common validation engine.
Posted by RyamTJ on May 12, 2008 at 11:50 AM
@RyamTJ: Validat works exactly the same way ... it is really synonymous to your individual validation beans except with Validat you can setup different validation configurations for different states that a bean might be in. Then, when it comes time to validate data, you can pass either the dirty bean or a collection of data to Validat and let it handle the validation, sharing any number of custom validation rules across your application.
Posted by Jeff Chastain on May 12, 2008 at 11:55 AM
@All,
So, if we have an external validation service for the individual beans, then what function to the beans serve? Are they, at that point, just objects that hold properties. It seems that if we can set bad data into them, to be validated at a later point, then the bean is really nothing more than a CFC that has a list of gettable and settable properties. If you have a base CFC as I do in my example, then really, we could quite literally have a CFC that has nothing but a list of VARIABLES.Instance = {} default values.
Something about this seems odd. I guess this is part of that ongoing debate between the "Anemic Domain Model" and "Smart Objets". At this point, why even have the CFC? Why not just have a Struct as the lightweight transfer object that can be validated later. It's not like I am actually using type checking on CFC type (methods that accept a CFC are always using type="any").
When I think about this, I can't help but think about what I discussed with Hal Helms, and the idea of "Idealized Objects" [ http://bennadel.com/index.cfm?dax=blog:1134.view ]. To me, this property-bean seems like it adds no value.
Now, while I am very new to OOP, I understand that the principle of "taking what changes and factoring out and encapsulating it" and the idea of a an external validation service really does go along with that point. Of course, is this one of those things that should be factored out from the beginning? Or is this something that needs to be factored out when it becomes an issue?
Posted by Ben Nadel on May 12, 2008 at 3:15 PM
@Ben -
In some cases I can see beans being as "dumb" as you mentioned with only basic getter/setter methods. But if you did not have the bean object, where would functionality like "getAge()" go when a Person only has a birthdate?
Beans can have a lot more functionality to them and I don't see validation logic as being the solution to making a bean non-anemic. One of the big goals of a good object model as I see it is to extract common code from multiple places (i.e. validation checks) and put it into one object or service so that it can be easily maintained.
Posted by Jeff Chastain on May 12, 2008 at 3:19 PM
@Jeff,
Good point. I forgot about property-based methods.
Also, I took a look at the Validat examples on the Alagad blog; I didn't see anything about error messages - just validation "types". Do you define your user-friendly error messages outside of Validat?
Posted by Ben Nadel on May 12, 2008 at 3:22 PM
@Ben -
In the XML definition for Validat where you define which rules (assertions) should be applied to which fields (dataElements), thee is a property there called message. That message property can contain either plain english text or in most cases, I will have it return a resource bundle key that I can go to my localization setup and get back a string to display.
For example, the following snippet defines a data element named 'firstName' which is required and must also be between 1 and 100 characters in length.
... opps, can't paste XML into your blog ... check out this link instead to view the file in question: http://svn.alagad.com/validat/trunk/examples/formValidation1/_config/validat.xml (line 33-40)
Note, there is a message property on the dataElement tag which applies to the required state as well as another message tag within the parent assert. The second message is a tag because it is possible the assertion might return different messages for different events although the preference is usually to keep assertions pretty simple and reusable.
Posted by Jeff Chastain on May 12, 2008 at 3:36 PM
@Jeff,
Ok cool. Thanks. Sorry I missed that earlier.
Posted by Ben Nadel on May 12, 2008 at 3:39 PM
I have tried to take the data validation logic and factor it out into validation behaviors:
http://www.bennadel.com/index.cfm?dax=blog:1225.view
I like where this is going. It seems like a lot of extra work, but it's not really. It's just taking existing code and moving it around.
Posted by Ben Nadel on May 13, 2008 at 7:41 AM