OOPhoto - Moving To OOP - Adding Service Objects
The latest OOPhoto application can be experienced here.
The OOPhoto code for this post can be seen here.
Yesterday morning, I launched phase one of OOPhoto, my latest attempt at learning object oriented programming (OOP) in ColdFusion. And, while this is an attempt at learning OOP, phase one consisted of a working application written entirely with old-school procedural-style code. I was getting so caught up in the thought process of creating an object oriented style application that I was slowly slipping into analysis paralysis. To get past this problem, Dan Wilson and Peter Bell suggested that I just bang out the fastest, simplest solution first, and then worry about refactoring it into OOP later. So, that's what I did:
The site, while quite small, was much more complicated that expected because of the AJAX commenting and photo uploads. While a seemingly simple feature, it adds a lot of overhead to the application (at least in my amateur way of doing it). Plus, I think the fact that the application has both internal (ColdFusion) and external (AJAX) touch-points will make it a better overall leaning experience when we convert the application to object oriented programming complete with remote facades.
Yesterday afternoon, after launching the application, I took a trip down to Systems Forge to meet with Peter Bell. Peter was kind enough to sit down with me and go through my procedural style application pointing out ways in which it could be refactored. He mentioned several things, including a conversion to CFC-based controllers; but, in the end, we felt that the easiest first step for an OO-n00b like me would be to create simple Service objects to factor out my queries. Now, this doesn't really make my project more Object Oriented, but it does move me in that direction and it certainly encapsulates my queries and cuts down on repetition (keeping my system DRY'er that it was originally).
Furthermore, I am going to have the Service Objects simply return queries. I love the query, I know the query, I am friends with the query, I think the query looks great in overalls - I'm not ready to cut her out of my life just yet. I will worry about different types of return values later on in my journey.
Defining the service objects and methods was simply a matter of walking through the application and defining the "intent" of each of my queries. Here is what I have come up with:
PhotoGalleryService.cfc
- GetGalleriesByKeyword()
- GetGalleryByID()
- GetGalleryByJumpCode()
PhotoService.cfc
- GetPhotoByID()
- GetPhotosByIDList()
- GetPhotosForGallery()
- GetRecentlyUploadedPhotos()
- IncrementPhotoViewCount()
CommentService.cfc
- GetCommentsForPhoto()
That's all the SELECT-style queries that I have in my application. And I say "SELECT-style" because you might notice that there are no persistence-oriented queries in my service objects; I have decided to leave the persistence for later. For now, I am just going to concentrate on pulling out information in a refactored way.
While this step is rather small in that it is only taking queries and moving them into objects, it will end up serving several important purposes:
It will get me more comfortable with getting my information from objects rather than hand coding all my queries for each page. Not only will this cut down on my repetition, it will also force me to not worry about optimizing every query for every page. By that, I mean that I will have to become comfortable that a query might return more information than is needed on a given page. You might laugh, but this is an odd point of control to give up.
It will get me more comfortable with Factory-created objects and dependence injection. While I am new to object oriented programming (OOP), I am familiar with some CFC "Best Practices". With all my queries being moved to CFCs, I know that I have to start injecting my DSN information into the object for use in the CFQuery tag (such that the CFC does not need to know about the universe outside). This will be a nice and easy first step before I get into more complicated dependency injection issues later on.
Ok, time to code my Service Objects.
Reader Comments
Looking great! I was thinking about it and if I had to come up with a decent general roadmap for refactoring for procedural to OO it'd probably be:
Pull out queries into service classes - to start with, just createobject them inline and allow them to speak to global scopes if necessary. Start with one service class per business object.
Start passing the dependencies in and out of the service classes explicitly, passing in DSN, passing back the results as a return value. If you need any other properties in the service class, pass it in using a struct as a "bucket" to hold it all. I call mine PageRequest.
Next I'd probably move to cfc controllers so you start to have an object based framework.
From there, you now have enough objects that it makes sense to move to a DI framework like ColdSpring or Lightwire to create and return your objects.
Once you get to that point, you might want to create DAOs within your service classes.
From there, I'd consider turning queries into IBO's so you have calculated getters and setters, can put validation logic in the bean, ca do things like Order.getItems() for traversing object graphs, etc.
Clearly not the "one true way", but probably not a bad approach for breaking down OO and starting to get a feel for how it should work.
Another approach would be to pick an OO framework like Model Glue and use that to do some of the heavy lifting. It'd probably be quicker and would have other benefits, but I don't think it's bad to have the learning experience of building your own simple OO framework. From there you can then port fairly easily to a community framework if it makes sense for your project.
@Peter,
Looks like a good road map. I think I'd like to start off trying some hand-written DI stuff in my Factory with these Service updates. I think I understand dependency injection in practicality; it's the automation based on XML or object lists that I think makes it complicated.
As much as things like LightWire or ColdSpring can do the "heavy lifting" for me, I'd like to suffer through the heavy lifting on my own as much as possible in this project (at least in the first few phases). I don't want to try to implement anything without feeling that pain that it alleviates.
Peter, thanks so much for your hands-on help yesterday. Looking forward to more feedback in the future.
Sounds like a plan! I think waiting until you feel the pain and then understanding why you're solving each problem is a great approach! Whenever you want we can sit down and do another session!
Ben...
At the end of this, are you going to be honest with yourself about whether or not this is "worth" it? I'd like to see your take on where the breakpoint is between basic OO, splitting everything out into CFCs, vs full on OO, controllers, views, etc.
Also, once you're done with this article, you should really consider submitting part, or all, of this to a website like Sitepoint.com or something.
I got started in programming because of an article on Sitepoint called "Building database driven websites with PHP and MySQL". An article of this scope could be hugely beneficial to a newcomer because of your great attention to detail.
Keep up the good work.
Ben,
I've been following your posts with excitement because I've never quite been able to break into OOP. Working on a project last night I threw up my hands in disgust and decided to go back to my old system. Today I log on and "whammo!" a post that directly relates to where I am right now. Sweet! Now I can't wait to get back to that project and start on those service objects!
Keep up the good work! You are inspiring OO noobs all over (well, at least this one).
Hi Ben,
I'm right there with you. I'm kind of in the 5/1 syndrome of Bean, DAO, GW, Service, controller.
However, I keep focusing on Bell and Corfield's stuff about OOP in all the ColdFusion Quarterlies. Corfield has got me thinking about grouping stuff together and whacking out the basics, while Peter often shows where it's possible to go.
I definitely find that stuffing my service full of crap is the way to go.
From there I work to plump up my beans from store brand to Hanover. In the process, I end up with more beans, but less in the service cfc.
I'm still wrapping my head around Bell's IBO, but I think that Corfield is right about the 5/1 syndrome being the "wrong" recipe, but so easy to get stuck on. You end up with so many workarounds and frustrating dead ends that OOP seems like a false religion that people implement because they are too smart for ColdFusion.
ColdBox has helped a little with this. I love ColdBox. It was the first framework that made sense (due to the documentation and examples).
Service objects are in place:
www.bennadel.com/index.cfm?dax=blog:1293.view
@Andy,
You make a good comment about staying honest. Thanks to you, I am going to be putting a "Object Oriented Reality Check" at the end of future OOPhoto posts.
@Dan, Justin,
I feel your pain. I hope that we can all learn something from this.