OOPhoto - Simple Service Objects In Place

Last week, I quickly coded the procedural version of OOPhoto, my latest attempt at learning object oriented programming in ColdFusion. After getting some good feedback from Peter Bell, I decided that the easiest next step would be for me to create some very simple service objects. This wouldn't entail much more than taking my SELECT-style queries and moving them into ColdFusion components. But, as easy as this would be, I decided that it would have some good benefits, summarized here:

I would get more comfortable retrieving my data from objects and not having to worry about optimizing every query.

I would get more comfortable with the dependency injection (DI) needed to move the data source name (DSN) into each service object.

Let's start out with the Dependency Injection as this is an important concept and one that makes complex object creation easy. Dependency Injection, or DI, sounds tricky, but in really it is just the moving of required objects into other objects. ColdFusion has a huge advantage in this area over other languages because in ColdFusion, object instantiation and object initialization are two separate steps. In fact, object initialization is not really something that is inherent to ColdFusion objects at all; sure, there is the pseudo-constructor, but things like the Init() method have emerged as an industry "best practice" and is in no way a required part of creating objects in ColdFusion.

Because object "preparation" uses these two different steps, it is quite easy to inject dependency objects after step one but before step two in the following manner:

The simple fact that this behavior is available in ColdFusion makes creating circular references and other complex objects much less of a headache than it could be.

To enable our service objects to have their dependency objects injected into them during object creation, we have all the service objects extend a BaseService.cfc which has the utility method InjectDependency():

<cfcomponent

output="false"

hint="I provide the based functionality for all Service objects.">

<cffunction

name="InjectDependency"

access="package"

returntype="any"

output="false"

hint="I inject dependency objecst into the VARIABLES scope of the extending Service object.">

<!--- Define arguments. --->

<cfargument

name="Property"

type="string"

required="true"

hint="The key at which the dependency will be stored."

/>

<cfargument

name="Dependency"

type="any"

required="true"

hint="The dependency object that is being injected into the extending service object."

/>

<!--- Inject the dependency object. --->

<cfset VARIABLES[ ARGUMENTS.Property ] = ARGUMENTS.Dependency />

<!--- Return this object for method chaining. --->

<cfreturn THIS />

</cffunction>

</cfcomponent>

The InjectDependency() method does nothing more than take another object and store it in the VARIABLES scope of the target object. While this method could be dynamically added at runtime to all service objects, I decided to keep things as simple as I could for as long as I could. Having the method as part of the object that has package-only access seemed like the easiest realization of this utility.

Now, the InjectDependency() method is only a weapon in the battle; we still need someone to wield that weapon effectively. In our application, that warrior is the ObjectFactory.cfc. The Object Factory does nothing more than encapsulate the method by which other objects are created. So, for example, instead of calling something like:

<cfset CreateObject( "component", "MyObject" ).Init() />

... you would "ask" the object factory to create you an object of that type:

<cfset Factory.Get( "MyObject" ) />

This prevents the calling page from having to know about any aspect of object creation other than the unique name of the object itself. This includes things like object paths, knowledge about Init() arguments, and, most importantly, any kind of dependency injection that needs to be done.

The OOPhoto object factory is created as a singleton (only one instance ever exists) and is cached in the APPLICATION scope:

<cfcomponent

output="false"

hint="Creates, intializes, and wires together all objects that are needed in the system.">

<!--- Check to see if the given object exists in our singleton cache. --->

<cfif StructKeyExists( VARIABLES.Instance, ARGUMENTS.Type )>

<!--- Return the cached singleton. --->

<cfreturn VARIABLES.Instance[ ARGUMENTS.Type ] />

<cfelseif (ARGUMENTS.Type EQ "ErrorCollection")>

<!--- Return new instance. --->

<cfreturn THIS.CreateCFC( ARGUMENTS.Type ).Init() />

<cfelse>

<!---

If we have gotten this far, then an unknown object has

been requested. Throw an exception.

--->

<cfthrow

type="OOPhoto.UnknownObjectType"

message="You have requested an unknown object type."

detail="The object type you have requested #UCase( ARGUMENTS.Type )# is not valid for this object factory."

/>

</cfif>

</cffunction>

</cfcomponent>

Notice that in the Init() method of the object factory, I instantiate my three service objects. Then, I inject the dependency object (DSN). Then, as a final step, I call the Init() method on the object so that it can take care of any initialization that needs to be done internally. There are whole frameworks out there that take care of this kind of wiring for you, but for applications as simple as this, so far you can see there is no overhead to wiring this together yourself.

The service objects themselves are not that interesting, so I won't bother showing them all to you. I merely took the queries from the procedural pages and moved them into organized ColdFusion components. The real change that I made was to make the queries a bit more generic. So, for instance, on a page where I really only needed the ID and name of a record, I end up returning the entire record since I am not sure what the calling page will need.

This was a small step in my application, but I think, a good step in the right direction. It was nice to see that Dependency Injection was much easier than I ever expected it to be. This just goes to show you that things you don't know or don't understand are often times much grander in your head than they are in reality. This is a good lesson to keep in mind as we continue on our journey to understanding object oriented programming.

So what's the next step? As I think Peter Bell's advice worked well for this last step, I might as well not switch horses in mid-stream; I'll go with Peter's next suggestion which was to create CFC-based controllers. Right now, my front-controller consists of several nested CFSwitch statements. CFSwitch statements are extremely easy to build and I have to say that I am a bit skeptical as to the benefits of a CFC-based controller; but, I will proceed regardless as come of Peter's arguments did make sense.

Object Oriented Reality Check

In my last post, Andy Matthews asked me if I would be honest with myself at the end of this process. To make sure that I stay honest, not only with myself, but with all of you, my dear readers, I have decided that at the end of each of these posts, I will start putting a brief "Reality Check" section with the following questions:

Was This Step Worth Implementing?

I think this step was definitely worth it. While some of the queries that I moved into the service objects were one-off style queries, several of them were repeated throughout the application. By moving them into service objects, I keep my application DRY (do-not repeat yourself) and I help to ensure that any changes to the queries need to be done in a minimal number of places.

Is My Application Object Oriented?

Not even close. All we have done is refactored some code to remove duplication and implemented some patterns that make object creation easier. In reality, we have done nothing more than moved our procedural code into user defined functions (UDFs) that happen to be cached in object instances. All we have done is made our procedural code more organized; there are no aspects of object oriented programming present at this point.

Fixed the "length required" issues. Some of my PhotoService.cfc methods required that a photo be joined to a gallery in an INNER JOIN fashion. Of course, not every photo can do that (photos uploaded for new galleries). So, no data was being returned. The code has been updated.

@Ben, have you tried LightWire for the DI? I think rather than writing methods to handle that for you, you should take a look at it. I've been using it for quite some time and can't see building any app wtihout it. It leaves me to only think about what I need from my objects vs. thinking of what i need to pass to what to make it work. I've been reading your posts on the learning OO and it's one of those things that will help you along faster.

I want to try to avoid all third-party frameworks until I feel that I have a good handle on all of this stuff. Until I know what problem they solve, I am not sure that I will be able to leverage them effectively.

But to answer your question, yes, I have seen LightWire. I've even been lucky enough to have Peter Bell (author) go over it with me personally.

I think I pass DNS through typically as a "source," "username," and "password". Although, with ColdFusion 9 having an app-wide DSN, I might stop using that approach. Also, I think my start to just put my username/password information in the DSN setup in the admin.