OOPhoto: Adding A Facade Layer
The latest OOPhoto application can be experienced here.
The latest OOPhoto code can be seen here.
I have decided to move ahead with the concept of a Facade layer in OOPhoto, my latest attempt at learning object oriented programming in ColdFusion. The Facade layer is going to have one unifying property: none of its methods will accept a business objects as an argument. The reasoning behind this decision is that for a method to accept a businesss object it requires the calling page to have an understanding of the way in which data is processed (ie. how simple data is transformed into businesss objects). If we require this level of understanding to exist in the calling page (the Controller in our area of study) then it will require us to duplicate knowledge in too many places (especially once we get into Remote Facades).
And plus, this is going back to what Dan Wilson was telling me about "processing data"; we don't want the Controller to do anything with the data it gets - we want the Controller to hand off the data to another object of processing. Remember, that is all the Controller is supposed to do - take actions and hand off control.
The first thing I did was literally go through each ACTION file in my application (included dynamically by the Controller) and look at all the "gestures" that the Controller was performing. When I did this, I thought in terms of simple data, not objects:
- SaveComment( id, comment, photo_id )
- DeletePhotoGallery( id )
- GetPhotoGallery( id [, throw_exception] )
- SavePhotoGallery( id, name, description, photo_id_list )
- GetPhotoGalleryByJumpCode( jump_code )
- GetPhotoGalleriesByKeyword( keywords )
- GetPhotosByIDList( photo_id_list )
- GetRecentlyUploadedPhotos()
- GetPhoto( id [, throw_exception] )
- IncrementPhotoViewCount( id )
- GetCommentsForPhoto( id )
- UploadPhoto( file_field )
One difference you might notice here (if you have photographic recall of my code) is that I never had the concept of IncrementPhotoViewCount() before. Currently, when a user goes to view a photo, I literally increment the ViewCount property on the Photo object and then save the Photo (via Photo.Save()). In this scenario we are seeing a strong coupling of the Controller to the implementation of photo-view tracking.
When I saw this, I couldn't figure out how to get around saving a photo without using a Photo bean. That's when it occurred to me that there was something fundamentally wrong with the way I was handling this "gesture". Messing with the Photo properties was too specific - too detail oriented - that wasn't the true "gesture." The true gesture was updating the view count. I know this sounds exactly the same, but using the new method both the intent and the distribution of responsibility are quite different.
At first, I was thinking of creating just one Facade object. But once I had the above list, I decided that it would be better to break these gestures up into business's-object-related verticals. This way, if I needed to add more functionality, I wouldn't end up creating a GOD object.
PhotoGalleryFacade
- DeletePhotoGallery( id )
- GetPhotoGalleriesByKeyword( keywords )
- GetPhotoGallery( id [, throw_exception] )
- GetPhotoGalleryByJumpCode( jump_code )
- SavePhotoGallery( id, name, description, photo_id_list )
PhotoFacade
- GetPhoto( id [, throw_exception] )
- GetPhotosByIDList( photo_id_list )
- GetRecentlyUploadedPhotos()
- IncrementPhotoViewCount( id )
- UploadPhoto( file_field )
CommentFacade
- GetCommentsForPhoto( id )
- SaveComment( id, comment, photo_id )
Clearly, some of these methods are going to just forward the message onto the appropriate Service objects, but for many of them, we are going to be encapsulating the processing of data. I'm not 100% sold on this concept yet, but I figured I better move forward with something before I fall back into the world of analysis paralysis.
Reader Comments
Seems sensible enough, and grouping into 'chunks' that handle the same sort of stuff is good separation of concerns.
Why did you choose to use simple lists of primitive types rather than a strongly typed Value (Transfer) Object ?
@Tom,
What is a strongly typed Value Transfer Object?
Just an object which only has properties.
In ColdFusion, this is easiest to think of as a CFC with CFPROPERTY tags.
If you send and receive only VOs to and from the Facade, there is no way you can send a string to a number property or similar. It also defines exactly what it is you are sending (where as one set of (id,name) looks much like another set of (id,name) UserVO and CompanyVO are very different, and it's clear what you are getting back.
Maybe I spend too much time only using ColdFusion for the back end (i.e. the View tends to be a Flex client).
@Tom,
I am not sure that the Value Transfer Object (VTO) adds any benefit here. As far as typing of data, I could use the type of the CFArgument in my Facade methods to make sure that a string couldn't be passed in for a number. However, the way my business objects currently work is that you can pass in any data you want to the object and it doesn't get cause concern until Validate() is called.
As far as the difference between district types of VTO (ie. User and Company), I am not sure what this accomplishes. Because my Facade methods are "gesture-based" (ie. DeletePhoto()), you already have the Controller and the Facade agree by way of invocation-contract on what the intent of the method call is.... If someone were to pass Comment ID to a DeletePhoto( ID ) call, yes, that could be a problem... but the same problem would be caused if someone passed in a VTO with invalid data?
But the real question is: if we used a VTO, what would the Controller have to know? Where does the VTO come from? What puts the data in the VTO before it gets passed to the facade? If we require a VTO, does the Controller have to know more or less than the target facade and the method signature?
Not VTO - VO (or TO). I use 'VO' to stop people being confused with something Transfer (the project) specific.
I agree there might be little immediate benefit to using VO right now. But if you image a Photo facade, it might have generic 'delete()' methods, and a User facade would also have a 'delete()' method - so you can indicate intent in the argument/return types.
You're also right to say using a VO doesn't stop someone sending a Comment to the Photo facade - strongly typed interfaces would reject the request earlier than using just an id number though.
"Where does the VTO come from"
It doesn't care (could be a CFML page, Web service, AJAX, ...). There has to be some shared knowledge of the contract between the 'client' and 'server' (where server == many Facades). Sharing VO types (and therefore properties) is a stronger contract than just a list of simple types.
I would say the user of the facade needs to know less (it only needs to know (facadeName,method,vo) rather than (facadeName,method,property1,propety2,...). Cuting and pasting all those lists of properties is both fragaile and a pain when a property is added to an object.
@Tom,
When I asked where the VO came from, I guess what I meant is - who populates it? At some point, FORM data has to become one (or more) VO's correct? Who does that?
The form's action page would transform the form fields into a VO and invoke the service.
@Tom,
If the form's action page has to populate the VO, then why not just have the action page move the data right into the business object (as I am doing now)? It seems that by using a facade with simple-values, the action page doesn't need to know how to package the data into a BO or a VO - it just needs to know how to pass off the named arguments.
I suppose I'd do it by making a VO to insulate myself from any future changes, or just out of habit :-)
@Tom,
I am not sure that insulates you and more or less. Let's run through a worst case scenario for both methodologies: Add a new property.
Using a VO:
* Update database and business objects.
* Update interface to reflect new field.
* Update VO to accept new property.
* Update action page to move new field into VO.
* Update processing method to handle new VO property.
Using a value-based method:
* Update database and business objects.
* Update interface to reflect new field.
* Update action page to use new argument in method call.
* Update facade method to use new argument.
* Update processing method to use new argument.
Unless I am missing something completely, it seems that both cases require equal amounts of work when updating. The difference is our choice of intermediary device. For you, its a VO, for me, it's a value-based facade method.
The impact of that difference, is that with a VO, the action page needs to know how to get the VO object to populate and with the facade, the action page needs to know what arguments to send.
It seems basically like the same thing.
If you don't use VO, you have to update the signature of every single method that uses the object to have the extra fields, even if that method doesn't need the new field.
@Tom,
But only one method in my facade uses value-based arguments - the "Save" method. For example:
SavePhotoGallery( ID, Name, Description, PhotoIDList )
No other method would need the entire set of arguments? At most, the other methods in the facade would only ever need the unique ID of the target object.
What methods were you envisioning? Maybe we are talking about two very different things.
What happens (in the future) when the services behind the facade call each other ? You wouldn't want each one to have to run a SELECT statement.
@Tom,
While I can't speak from true experience, what I am intending to do is always have the Facade layer turn around and call the Service layer using actual objects. I can't think of a reason why one Facade object would have to reference another Facade object. Once you are "inside" the Model, I have no problem passing around full-fledged business object and making direct calls the other services.
The Facade layer will only be invoked by the Controller.