CFTRY vs CFIF:
--------------
Lets say I want to store some data.  I run it through a filter to make sure
it's all correct (even though the source of the data might be competent and
have provided valid date).  If it's correct, then I try and store it, and
everyone is happy.  If it's not correct, I'll display an error message.

Lets say I want to store some data.  I know something might go wrong, so I
say "hey, if problem 'x' happens, I want to know".  If nothing goes wrong,
everyone's happy.  If something goes wrong, and it's exactly the 'x' that I
want to know about it, then I'll deal with it.

I see the route as "store the data", and if something out of the ordinary,
I'll address that when the time comes.  In other words, I don't see a fork
in the road, I see a single path that might be blocked, and I need to be
able to handle that potential eventuality.  The fork is a very procedurally
minded view on things, while the exception handler is more passing messages,
which is what OO is about.  Not to say that one is better, when I'm doing
Fusebox (procedural), everything is the fork in the road mentality, but when
it's Mach-II (OO), it's all exception handling.

'store' forces 'validate':
--------------------------
I think 'store' should throw an exception if it has invalid data, but it
shouldn't force the user to validate that data in a specific way.  And the
checks it should perform are likely different as well.  'validate' deals
with handing business rules.  'store' should pretty much just do type
validation for the persistance layer beneath it, and perhaps some
interpreting of any errors that occur at the persistance layer.  They might
not be independantly implemented, but they should be independant in terms of
the public interface of the object.  I shouldn't have to call 'validate' in
order to call 'store', in other words.

Here's a grossly simplified version of what I understand to be your mechansm
(correct me if i'm wrong), using the empty string as an "it's all good"
value to return from 'validate':

function validate() {
  this.validateCalled = true;
  if (len(name) lt 5)
        return "name must be 5 chars";
  if (age LT 18)
        return "you must be 18";
  return ""
}
function store() {
  if (NOT this.validateCalled)
        throw ValidateNotCalledException
  if (validate() NEQ "") {
        this.validateCalled = false;
        throw InvalidStateException
  }
  // do my store
}

The same methods would look like this in my CFC:

function validate() {
  if (len(name) lt 5)
        return "name must be 5 chars";
  if (age LT 18)
        return "you must be 18";
  return ""
}
function store() {
  if (NOT isNumeric(age))
        throw InvalidStateException
  // do my store
}

Both perform exactly the same, but my methods are rather more flexible with
regard to how the calling code can use the object.  Perhaps we have certain
circumstances that we want to store values taht violate business rules, but
are valid for some reason (I occasionally use negative userIDs to represent
superusers, for example).  Or we might want some objects to have tigher
restrictions on their data than what 'validate' enforces (superusers must
have longer passwords).  In the former case, I'm SOL with your methods, and
in the later, I'm doubling the work I have to do (validate the tight rules
and then validate the CFC so I can store).

I'm also finding that 'store' is often within a totally different object.
Take an app that's designed to run on several persistance backends, and it
will dynamically switch based on an config parameter.  The 'validate' method
will never change (business rules are business rules), but the persistance
mechanism might (Access, MySQL, Oracle, SQL Server, XML file).  For this
type of architecture, I need to have an easy way to swap out the 'store'
method, without affecting the rest of the object.  There are many solutions,
but my preference is to have a single object for each persistance layer type
with methods such as persistPage, persistUser, etc., each of which takes an
object of the specified type and persists it.  So I have an AccessPersister,
a MySQLPersister and so on.  In this case, 'store' and 'validate' aren't in
the same object.  They'll be called like MySQLPersister.persistPage(myPage),
rather than myPage.persist().  That keeps the backend totally abstracted
from everything else.  Just instantiate a persister on application startup,
and then all my code can call that persister whenever they need anything.

Well, that got really long, really fast.



> -----Original Message-----
> From: [EMAIL PROTECTED] [mailto:[EMAIL PROTECTED]
> Behalf Of cf_nut
> Sent: Monday, October 06, 2003 4:50 PM
> To: [EMAIL PROTECTED]
> Subject: Re: [CFCDev] Data validation
>
>
> Interesting, Barney. If you apply your modification (single validate,
> perhaps in the method hasErrors() ), to Nathan's approach (validate
> in every setter method) then you arrive at something that's close to
> my starting point  :-) .
>
> My reasons for having a single validate() were similar to yours. In
> particular, in the case of object data collected by by a 'wizard', or
> multi-step update process, it may not make sense to do validation
> until you've collected all the data and it's *all* represented in the
> instance data of the object.
>
> I'm genuinely interested in why you prefer to handle validation
> errors by exception, as in your original reply to my message.
> Although we're both achieving the same thing, to my mind
> it's 'cleaner' somehow to say 'which route am I going down, doing a
> store, or showing an error message to the user ?' as distinct from
> your approach where you seem to have the possibility of 'right, I'm
> going to store ... whoops, no, shouldn't be here (got a validation
> error) I'll have to go display an error message'.
>
> I'm interested, too, in why you prefer store() not to throw an
> exception as I suggested. To my mind it's the reponsibility of the
> application to ensure valid data is stored. And there's no better
> place to enforce the validation for an object than in the component
> code for that object. If, as you seem to be suggesting, you separate
> validate() and store() completely, then that makes it the
> responsibility of the calling code to call validate() and gives that
> code the option of storing invalid data by omitting (through design
> or programmer error) the validation step. By making a linkage as I
> suggested (store can't proceed till validate is done, enforced by
> throw) data integrity is preserved no matter what, and the writer of
> the calling code is forced to call validate() (and cope with any
> resulting errors) to get store() to succeed.
>
> As you say, it's amazing how many different approaches people find to
> the same problem. :-)
>
> Andy.
>
> --- "Barney Boisvert" <[EMAIL PROTECTED]> wrote:
> >
> > How did you handle multi-datum validation?  Like ensuring a
> password doesn't
> > contain a username, for example.  I gone down the road of
> validating in the
> > setters a couple times, and always ran into that wall at some
> point.  You
> > need to be able to have the object in an invalid state between the
> setter
> > calls, but you need to first error to be cleared after validating
> the second
> > datum.  That's the primary reason I've always opted for a
> centralized
> > validation method.
> >
> > It's amazing how everyone has totally different ideas for solving
> the same
> > problem.
> >
> > barneyb
> >
> > > -----Original Message-----
> > > From: [EMAIL PROTECTED] [mailto:[EMAIL PROTECTED]
> > > Behalf Of Nathan Dintenfass
> > > Sent: Monday, October 06, 2003 2:01 PM
> > > To: [EMAIL PROTECTED]
> > > Subject: RE: [CFCDev] Data validation
> > >
> > >
> > > In that case there as a baseContentObject that contained the basic
> > > machinery -- the "fields" were defined in CFPROPERTY tags in CFCs
> that
> > > extended the baseContentObject.  It was a very "fat object" way
> > > of thinking,
> > > and the need to introspect the meta data at run-time proved to be
> overly
> > > burdensome from an overhead perspective.
> > >
> > > That said, the basic concept of having an instance always have
> an "error
> > > state" could easily be done without the other abstractions.
> > >
> > > Basically, it had an array of errors set up on init() -- the
> hasErrors()
> > > method really just, internally, tested arrayLen(instance.errors);
> > >
> > > Then, whenever setting data it would automatically be validated.
> In that
> > > case, it was based on meta data in the CFPROPERTY tag (each field
> > > had a list
> > > of "rules", each of which was a component with a standard
> interface), but
> > > there's not reason it needs to be that abstracted.  In other
> > > words, the act
> > > of saying something like:
> > >
> > > user.setName("Nathan","Dintenfass");
> > >
> > > would automatically validate that data -- if there was something
> > > wrong with
> > > it, an "error" (in that case, a CFC instance, but could just as
> > > easily be a
> > > struct or even just a string, depending on your implementation)
> was
> > > generated and appended to the array of errors held internally.
> > >
> > > Bottom line: I'm not totally convinced the "error state" method is
> > > necessarily a best practice, though if are already adopting
> a "fat object"
> > > paradigm it might make for nicer code than the need to manually
> validate
> > > and/or catch exceptions (which was the original reason for using
> it -- to
> > > make the API as flexible as possible while maintaining the
> > > integrity of the
> > > data).
> > >
> > >
> > >
> > >
> > > > -----Original Message-----
> > > > From: [EMAIL PROTECTED] [mailto:[EMAIL PROTECTED]
> > > > Behalf Of Barney Boisvert
> > > > Sent: Monday, October 06, 2003 1:37 PM
> > > > To: [EMAIL PROTECTED]
> > > > Subject: RE: [CFCDev] Data validation
> > > >
> > > >
> > > > I like that mechanism.  Where did you put your validation code?
> > > > 'hasErrors'?  If so, didn't you have to duplicate it
> in 'getErrors'?
> > > >
> > > >
> > > >
> > > > > -----Original Message-----
> > > > > From: [EMAIL PROTECTED] [mailto:[EMAIL PROTECTED]
> > > > > Behalf Of Nathan Dintenfass
> > > > > Sent: Monday, October 06, 2003 12:34 PM
> > > > > To: [EMAIL PROTECTED]
> > > > > Subject: RE: [CFCDev] Data validation
> > > > >
> > > > >
> > > > > Just to throw another approach into the discussion, one thing
> > > I did when
> > > > > building Modus (now defunct) was to have the instance have an
> > > > > error state at
> > > > > all times.  I then called my validation method(s) whenever
> > > > > populating data.
> > > > > The end developer code would then look something like:
> > > > >
> > > > > if(NOT obj.hasErrors()){
> > > > >       obj.store();
> > > > > }
> > > > > else{
> > > > >       errorsToShow = obj.getErrors();
> > > > > }
> > > > >
> > > > > The store() method could then throw an error if hasErrors()
> > > > returns true.
> > > > > Thus, the end API was highly flexible, allowing an
> application to do
> > > > > whatever it needed to do -- while maintaining the internal
> > > > > integrity of the
> > > > > data (you couldn't commit your changes to the instances if
> there were
> > > > > errors).
> > > > >
> > > > >
> > > > > > -----Original Message-----
> > > > > > From: [EMAIL PROTECTED] [mailto:[EMAIL PROTECTED]
> > > > > > Behalf Of Hagan, Ryan Mr (Contractor ACI)
> > > > > > Sent: Monday, October 06, 2003 12:13 PM
> > > > > > To: '[EMAIL PROTECTED]'
> > > > > > Subject: RE: [CFCDev] Data validation
> > > > > >
> > > > > >
> > > > > > I can't say which method (if any) is the "correct" method,
> > > > but I can see
> > > > > > some benefit to having your store() method be a little
> dumb.  I
> > > > > > don't think
> > > > > > that you should "definitely" have store() call validate().
> > > I may have
> > > > > > perfectly legitimate reasons for wanting to serialize "bad"
> > > > > data, such as
> > > > > > storing a "900" year old "person".
> > > > > >
> > > > > > Possibly an even better implementation would be passing your
> > > > > > store() method
> > > > > > an optional parameter (defaulted to true) that indicates
> > > > > whether or not to
> > > > > > call the validate() method?
> > > > > >
> > > > > >
> > > > > >
> > > > > > -----Original Message-----
> > > > > > From: Barney Boisvert [mailto:[EMAIL PROTECTED]
> > > > > > Sent: Monday, October 06, 2003 3:03 PM
> > > > > > To: [EMAIL PROTECTED]
> > > > > > Subject: RE: [CFCDev] Data validation
> > > > > >
> > > > > >
> > > > > > I'm not saying that 'store' shouldn't validate, it
> DEFINITELY
> > > > > > should, what I
> > > > > > am saying is that 'store' shouldn't care about how the
> > > > > validation process
> > > > > > works.  In Andy's original post, he said that 'store' would
> throw an
> > > > > > exception if 'validate' wasn't called first, and that's
> totally
> > > > > wrong.  It
> > > > > > creates a very tight coupling between the 'validate' and
> > > > > 'store' methods,
> > > > > > which are relatively unrelated in terms of the public API of
> > > > the object.
> > > > > >
> > > > > > Here is a perfectly legit implementation of the 'store'
> > > method, using
> > > > > > validate, though it is definitely less effecient than it
> could be:
> > > > > >
> > > > > > <cffunction name="store" ...>
> > > > > >   <!--- arguments --->
> > > > > >
> > > > > >   <cfif structCount(validate())>
> > > > > >     <cfthrow type="InvalidStateException" />
> > > > > >   <cfelse>
> > > > > >     <!--- do the store --->
> > > > > >   </cfif>
> > > > > > </cffunction>
> > > > > >
> > > > > > A better route is probably to have a private method that
> returns
> > > > > > a bitmap or
> > > > > > something indicating which validation errors that both
> > > 'validate' and
> > > > > > 'store' will call.  Store will just look and see if it's
> > > > non-zero, while
> > > > > > validate will unmap (is that the right term) the bits, and
> > > > > convert them to
> > > > > > textual error messages to return to the calling code.  That
> > > > > also keeps the
> > > > > > messages (which are really part of the presentation layer)
> > > > > > separate from the
> > > > > > logic.
> > > > > >
> > > > > > barneyb
> > > > > >
> > > > > > > -----Original Message-----
> > > > > > > From: [EMAIL PROTECTED] [mailto:[EMAIL PROTECTED]
> > > > > > > Behalf Of Raymond Camden
> > > > > > > Sent: Monday, October 06, 2003 11:52 AM
> > > > > > > To: [EMAIL PROTECTED]
> > > > > > > Subject: RE: [CFCDev] Data validation
> > > > > > >
> > > > > > >
> > > > > > > This is an interesting view, Barney, more so because it
> > > > seems wrong to
> > > > > > > me. Why would it not be the domain of "store" to also do
> > > a validate?
> > > > > > > Consider a Person CFC, which has an age property. The age
> > > must be a
> > > > > > > number greater or equal to zero, but less then 150 (or
> some
> > > > such). Why
> > > > > > > would "store" want to assume that you passed it valid
> data?
> > > > > Sure it can
> > > > > > > validate (using <cfargument>) that the value was a number,
> > > > > but it can't
> > > > > > > validate that it's a "proper" number. I guess it comes
> down
> > > > > to - why do
> > > > > > > you assume the caller will send you proper data? You say
> > > > validate is a
> > > > > > > tool to help the calling code - and I agree with that -
> but
> > > > > why can't it
> > > > > > > help other methods as well? I can certainly see the
> client calling
> > > > > > > validate() to ensure the data it wants to pass to store()
> > > > is safe, but
> > > > > > > I'd assume store() would assume the user is always
> forgetful,
> > > > > and should
> > > > > > > therefore do the anal thing and validate() as well.
> > > > > > >
> > > > > > >
> > > > >
> > >
> ======================================================================
> ==
> > > > > > > ===
> > > > > > > Raymond Camden, ColdFusion Jedi Master for Mindseye, Inc
> > > > > > > (www.mindseye.com)
> > > > > > > Member of Team Macromedia
> > > > > (http://www.macromedia.com/go/teammacromedia)
> > > > > > >
> > > > > > > Email    : [EMAIL PROTECTED]
> > > > > > > Blog     : www.camdenfamily.com/morpheus/blog
> > > > > > > Yahoo IM : morpheus
> > > > > > >
> > > > > > > "My ally is the Force, and a powerful ally it is." - Yoda
> > > > > > >
> > > > > > > > -----Original Message-----
> > > > > > > > From: [EMAIL PROTECTED]
> > > > > > > > [mailto:[EMAIL PROTECTED] On Behalf Of Barney Boisvert
> > > > > > > > Sent: Monday, October 06, 2003 11:52 AM
> > > > > > > > To: [EMAIL PROTECTED]
> > > > > > > > Subject: RE: [CFCDev] Data validation
> > > > > > > >
> > > > > > > >
> > > > > > > > I would highly recommend that your 'store' method does
> NOT
> > > > > > > > through an exception unless there is a problem.  Having
> it
> > > > > > > > check to see if 'validate' was called is not it's job.
> It's
> > > > > > > > job is to merely disallow the operation if it can't
> proceed,
> > > > > > > > and whether 'validate' was called has no bearing on
> that.
> > > > > > > > 'validate' is a tool to help the calling code determine
> what
> > > > > > > > an exception means in a way that can help it be
> resolved.
> > > > > > > >
> > > > > > > > You want me to structure my code this way (which is
> > > > > perfectly legit):
> > > > > > > >
> > > > > > > > <cfset errors = myCFC.validate() />
> > > > > > > > <cfif structCount(errors) GT 0>
> > > > > > > >   <!--- error display --->
> > > > > > > > <cfelse>
> > > > > > > >   <cfset myCFC.store() />
> > > > > > > > </cfif>
> > > > > > > >
> > > > > > > > But I like to do it this way, since we have an exception
> > > > > > > > handling mechanism in CF.  It just reads cleaner than a
> bunch
> > > > > > > > of CFIFs that are doing exception handling.  And in this
> > > > > > > > case, it'll also save me from generating that error
> struct
> > > > > > > > unless I need it.
> > > > > > > >
> > > > > > > > <cftry>
> > > > > > > >   <cfset myCFC.store() />
> > > > > > > >   <cfcatch type="IllegalStateException">
> > > > > > > >     <cfset errors = myCFC.validate() />
> > > > > > > >     <!--- error display --->
> > > > > > > >   </cfcatch>
> > > > > > > > </cftry>
> > > > > > > >
> > > > > > >
> > > > > > >
>
>
> ----------------------------------------------------------
> You are subscribed to cfcdev. To unsubscribe, send an email
> to [EMAIL PROTECTED] with the word 'unsubscribe cfcdev'
> in the message of the email.
>
> CFCDev is run by CFCZone (www.cfczone.org) and supported
> by Mindtool, Corporation (www.mindtool.com).
>
> An archive of the CFCDev list is available at
www.mail-archive.com/[EMAIL PROTECTED]

----------------------------------------------------------
You are subscribed to cfcdev. To unsubscribe, send an email
to [EMAIL PROTECTED] with the word 'unsubscribe cfcdev' 
in the message of the email.

CFCDev is run by CFCZone (www.cfczone.org) and supported
by Mindtool, Corporation (www.mindtool.com).

An archive of the CFCDev list is available at www.mail-archive.com/[EMAIL PROTECTED]

Reply via email to