By Peter Bell

The Importance of Putting Invalid Data in Business Objects

I know that there has been a fair bit of discussion about whether business objects should ever exist in an invalid state a while back. I strongly believe that in most cases you *should* allow invalid data in business objects. Here's why.

When most people start writing OO code, they often start writing POO (Procedural OO code) with business objects that are a glorified struct with getters and setters and with service classes containing all of their business logic.

The whole point of OO is to encapsulate related behavior and state to make our applications more maintainable. In most of the business apps I write I find the most common business logic pertains to object specific view logic (such as how to display currencies or capitalize names) which will want to go into a business object specific view helper (because it is view rather than model code), custom getters or (less frequently) setters for Product.getPrice() or User.getAge() or Product.getDiscount(User), and validation logic to check whether User.isValid(). The perfect place to encapsulate that is in the business object. Firstly, if you don't, you're going down the path of an anaemic domain model with service classes operating on VO's. Secondly, as your business validations start to depend on custom getters, you are going to have to duplicate logic. Let me give an example.

Lets say that only users over the age of 13 are allowed to register for our site. When a new user registers their form will contain a form.DateofBirth field. How do we validate this? We could write something in a UserService or RegistrationService that would turn DoB into Age and check it's > 13, but why not just have a private User.isValidAge() method called by a public User.isValid() that says something like:

return (getAge() GTE 13)

That way we're reusing our existing User.getAge() method and encapsulating all of the user validation within the user object.

I'm not saying this is the only way to code, but I'm still trying to get my head around the downside with this approach. I think it is important to realize that OO programming in and of itself has no business value. We don't gain anything from our program being OO. We gain something from our apps being more maintainable, and OO programming (when done right) can help to make our apps more maintainable. I have seen personally how encapsulating my business object validations within the object makes my apps easier (at least for me) to maintain. How exactly with a real world use case does putting business object validation within the BO make it harder to maintain the app?

Feel free to comment below!

Comments
Side note, good comments from Jeff that kicked off this discussion in the first place:

http://www.pbell.com/index.cfm/2009/5/21/Validate-...
# Posted By Peter Bell | 5/21/09 11:21 AM
I've usually solved this problem by having two layers of Business Objects. One, which represents the ideal "User", where only valid data is allowed to be entered, and one which represents the "UserRegistrationData", which allows any sort of data at all to be entered into it. Which one gets entered in to long term storage (i.e. database) depends on what the needs of the application are.

The reasons behind this approach are two fold:

1) There is often value in knowing that the information coming from the user is invalid or valid. Often invalid data needs to be used by various portions of the application to understand what the user is doing wrong, and how to instruct the user to correct their input.

2) Instead of having a "Validation Service" or other type of service to take the UserRegistrationData and validate it, The UserRegistrationData is always valid itself, the question then becomes whether it can convert itself into a valid "User" object. This can be accomplished by a "toUser()" method on the UserRegistrationData, which can throw validation errors. Therefore, you always have "valid" UserRegistrationData objects, and valid User objects, and when converting between the two of them, you can decide how to deal with errors.
# Posted By Adam Ness | 5/21/09 5:59 PM
That's definitely another approach. For me the issue would be that I'd have to duplicate functions like getAge() so unless one object extended the other I'd have to duplicate code. But i'm guessing the ideal structure will depend on the texture and feel of the kinds of validations you're dealing with . . .
# Posted By Peter Bell | 5/21/09 6:18 PM
It would depend on the needs of your application. If "getAge" was only used to validate a user, and never used by another portion of the application after that, you'd only need it on the UserRegistrationData. I suspect that the code would be a little different for that method between the two objects anyhow, since one is guaranteed to have a valid date, and the other has a "String" for birth date that was put in by the user and could be in some random format, or completely invalid.
# Posted By Adam Ness | 5/21/09 6:45 PM
@Adam,

I think what you're describing is the correct way to model the general case. Like most general solutions, it involves a bit more work. The rest of the discussion IMHO boils down to whether we lose much by taking a shortcut to the degenerate case where we collapse the candidate domain object onto the domain object itself and therefore relax the constraints on the domain model. Peter is arguing that most of the time we don't, and in many situations I'd go along with that.

There's one important exception, though, which is where we have created a formal application-independent domain model in code (as opposed to just in concept). One of the most important functions of a domain model is to act as the embodiment of the domain's constraints. This is even more important than encapsulating higher order business logic. Relaxing those constraints in any way undermines the value of the domain model.


Jaime
# Posted By Jaime Metcher | 5/21/09 6:48 PM
Good thoughts. I usually separate out "validation" per se into a couple different categories. One is more of a low level enforcement of data types. For instance, if some one entered the text "red" into the age box on the form, I can't imagine a reason where I would see fit to store that in the object. You would actually run the risk of getting run-time errors.

Now, as far as whether or not a given user object and its age property were valid for a specific registration is a different level of validation that requires knowledge of things outside of the realm of a "user".

Perhaps your site allows users to register for one of 10 different types of memberships and the age requirements differ for each. Is it really acceptable to expect the user object to have knowledge of the requirements for the memberships, or should you hand the user object to the membership and ask the membership to tell you if the user is capable of registering for it. TrialMembership.isValid(User)?

I like that approach a little better, but not much. This assumes you have a membership object to begin with, but why would you create one of those if you didn't even know whether or not your user was valid for it yet? Who creates new membership objects? The membership Service? membershipService.newTrialMembership(User) It almost feels as though the service is best suited to determine if a membership of that type can be legally created with the User object you have given it. Of course, now our model just became a bit thinner.

This is why I love and hate these discussions. I can go round and round and still hate most of the options I come up with. :)
# Posted By Brad Wood | 5/22/09 1:31 AM
"One of the most important functions of a domain model is to act as the embodiment of the domain's constraints."

I agree with that but I don't think re-using a 'User' object for validation has to dirty the model. User.cfc is a class, that can be instantiated one or more times for different purposes. Some of the instances can be stored in a persistent scopes and represent the clean, official model, while others can be instantiated for different purposes, like validation, without being pushed back into the model. You could have a separate function like save() to push a valid user back into the model. Something like:

<!--- create and populate a new transient user object --->
<cfset User = Service.newUser(form) />

<!--- if it is valid, push it back into the model --->
<cfif User.isValid()>
<cfset User.save() />

<!--- otherwise show the errors and discard the object --->
<cfelse>
<cfset SystemMessages = User.getErrors() />
</cfif>

Whats the alternative? Creating an intermediate object called User_Object_That_Is_99%_Similar_To_Main_User_Object.cfc?

Just some thoughts...
<br /><br /><br /><br />Testing br
# Posted By Baz | 5/22/09 12:58 PM
@Baz:

This is a good example of what I call the degenerate case (using "degenerate" in the mathematical sense, not as a value statement). If the candidate object and the domain object really are 99% the same, using the same implementation can be a good compromise. It's a perfectly reasonable idiom - I use it frequently myself.

Where this gets a little less clean is where there are significant differences in the allowed operations on a valid, saved object compared to a candidate object. When an object changes its set of callable methods, some of its properties, and the methods to which it can be passed as a parameter - all of which can happen after a save - it really belongs to a different class. You know this has happened when you start seeing a bunch of "if User.isPersisted()" conditionals throughout your code. In this case it can be cleaner to just create a new type to represent the changed status of the saved object.
# Posted By Jaime Metcher | 5/24/09 8:27 PM
Hi. This is interesting information. Maybe you're right. But nevertheless this is a controversial issue.

P.S. The best torrents search engine.с
http://www.queentorrent.com
# Posted By Bob Volsh | 8/26/09 2:28 PM
BlogCFC was created by Raymond Camden. This blog is running version 5.005.