Objects and Composition in CFCs

Posted on Jun 01, 2007

Ever since I released my code generator I have received requests for examples on how to use the generated code (a topic which I will revisit in my upcoming cfframeworks workshop). This is in part because it is being used by some people as a way to introduce themselves to programming with an object-oriented methodology, which wasn't really the intent of the application, but I am glad if it does help people with difficult transition.

Recently, I have been receiving very similar questions from a range of generator users all relating to how to implement object composition in ColdFusion. This article is intended to give an introductory overview of creating objects and, more specifically, objects composed of other objects using ColdFusion components. In order to do so, I have created a sample application which is attached to this post, more on that later, first a note on using the generator.

Using the Generator
Not to criticize anyone, but I think there is some confusion about whether the generator generates a finished product or not - it decidedly does not! The intent is to eliminate the grunt work of writing things like CRUD ,getters and setters and host of other repetitive tasks that you encounter writing OO applications. The generator does not handle composition and I don't intend to at the moment in part because I find foreign keys obnoxious and a particularly poor way to manage object relationships (yeah, this could probably be an entirely separate post). You will also find I modified some of the generated methods, and you can expect to see some of these changes make their way into the generator in an upcoming release.

Also, it is important to note that while my generator was used to get a head-start on the code, this article is not focused on how to use the generator and you could easily have hand-coded these examples.

As Usual - a Caveat
I am not an OO guru. I am writing based upon what has worked for me in the past, but I am always learning and improving my practices as it comes to OO. It is important to remember that there is more than one correct solution to this problem. I welcome feedback on the code here, and at the very least this begins a discussion of the best ways to handle these common tasks.

Our Sample Object
For the purposes of our example, I created an Xbox object (am I hinting about my wishlist? No, I am not that crass ;). The thought was to create something you can easily relate to that is made up of other objects - in this case, our Xbox object has controllers, accessories and games.

The Xbox and related objects also have behaviors, which is a key to creating an object model that doesn't fall into the anemic domain model antipattern. So, for example our Xbox has all the standard getters and setters but it also can load and unload a game and our control can use and remove the headset option. There are only a few behaviors included for example purposes, but I wanted to include them to make an important point. Since the Illudium generator, like many code generators, generates code based upon database metadata, it is very easy to fall into the trap of creating "objects" that are simply structures representing database data and nothing more. If you want to better understand what this is and why it is a bad thing, read the Fowler article or look into some of Hal Helms articles and presentations like this one.

You can see this by downloading the code attached to this post, but here is an example of my console control object which includes the standard generated code plus three custom methods - useHeadset,removeHeadset and isHeadsetInUse:

<cfcomponent displayname="control" output="false">
<cfproperty name="controlID" type="uuid" default="" />
<cfproperty name="wireless" type="Boolean" default="" />
<cfproperty name="headset" type="Boolean" default="" />

<!--- PROPERTIES --->
<cfset variables.instance = StructNew() />

<!--- INITIALIZATION / CONFIGURATION --->
<cffunction name="init" access="public" returntype="control" output="false">
<cfargument name="controlID" type="uuid" required="false" default="#createUUID()#" />
<cfargument name="wireless" type="string" required="false" default="" />
<cfargument name="headset" type="string" required="false" default="" />
<!--- run setters --->
<cfset setcontrolID(arguments.controlID) />
<cfset setwireless(arguments.wireless) />
<cfset setheadset(arguments.headset) />
<!--- you are not using the headset by default --->
<cfset removeHeadset() />
<cfreturn this />
</cffunction>

<!--- PUBLIC FUNCTIONS --->
<cffunction name="validate" access="public" returntype="array" output="false">
<cfset var errors = arrayNew(1) />
<cfset var thisError = structNew() />
<!--- controlID --->
<cfif (NOT len(trim(getcontrolID())))>
<cfset thisError.field = "controlID" />
<cfset thisError.type = "required" />
<cfset thisError.message = "controlID is required" />
<cfset arrayAppend(errors,duplicate(thisError)) />
</cfif>
<!--- wireless --->
<cfif (NOT len(trim(getwireless())))>
<cfset thisError.field = "wireless" />
<cfset thisError.type = "required" />
<cfset thisError.message = "wireless is required" />
<cfset arrayAppend(errors,duplicate(thisError)) />
</cfif>
<!--- headset --->
<cfif (NOT len(trim(getheadset())))>
<cfset thisError.field = "headset" />
<cfset thisError.type = "required" />
<cfset thisError.message = "headset is required" />
<cfset arrayAppend(errors,duplicate(thisError)) />
</cfif>
<cfreturn errors />
</cffunction>
<!--- ACCESSORS --->
<cffunction name="setcontrolID" access="public" returntype="void" output="false">
<cfargument name="controlID" type="uuid" required="true" />
<cfset variables.instance.controlID = arguments.controlID />
</cffunction>
<cffunction name="getcontrolID" access="public" returntype="uuid" output="false">
<cfreturn variables.instance.controlID />
</cffunction>

<cffunction name="setwireless" access="public" returntype="void" output="false">
<cfargument name="wireless" type="string" required="true" />
<cfset variables.instance.wireless = arguments.wireless />
</cffunction>
<cffunction name="getwireless" access="public" returntype="string" output="false">
<cfreturn variables.instance.wireless />
</cffunction>

<cffunction name="setheadset" access="public" returntype="void" output="false">
<cfargument name="headset" type="string" required="true" />
<cfset variables.instance.headset = arguments.headset />
</cffunction>
<cffunction name="getheadset" access="public" returntype="string" output="false">
<cfreturn variables.instance.headset />
</cffunction>
<cffunction name="useHeadset" access="public" returntype="void" output="false">
<cfif getHeadset()>
<cfset variables.headsetInUse = true />
<cfelse>
<cfthrow errorcode="com.xbox.controls.control.headsetNotSupported" />
</cfif>
</cffunction>
<cffunction name="removeHeadset" access="public" returntype="void" output="false">
<cfset variables.headsetInUse = false />
</cffunction>
<cffunction name="isHeadsetInUse" access="public" returntype="boolean" output="false">
<cfreturn variables.headsetInUse />
</cffunction>

</cfcomponent>

Handling Composition within the Bean
In the case of our example, each of the relationships is of a "has many" type as opposed to a "has a" - our console can have multiple controls, accessories and games, although in the case of controllers I put in place code to limit us to the four controller slots that exist on an Xbox. Each of the relationships are being stored in an array of objects that has a getter and setter, but also a means of adding an object to the array (a more complete application would likely require a method to remove an item from the array as well). Here are those three methods for the accessories array from console.cfc, as you can see they are pretty basic:

<cffunction name="setAccessories" access="public" returntype="void" output="false">
<cfargument name="accessories" type="array" required="true" />
<cfset variables.instance.accessories = arguments.accessories />
</cffunction>
<cffunction name="addAccessory" access="public" returntype="void" output="false">
<cfargument name="accessory" type="com.xbox.accessories.accessory" required="true" />
<cfset arrayAppend(variables.instance.accessories,arguments.accessory) />
</cffunction>
<cffunction name="getAccessories" access="public" returntype="array" output="false">
<cfreturn variables.instance.accessories />
</cffunction>

Using these methods, you could now do something like this to add an accessory to your console:

<!--- create a new console --->
<cfset myConsole = consoleService.createConsole(createUUID(),"XBOX 360",20) />
<!--- add our accessory --->
<cfset myAccessory = consoleService.createAccessory(createUUID(),"HD-DVD") />
<cfset myConsole.addAccessory(myAccessory) />

Building the Service
Since all of the functionality involved here is related, I have created a single service component that manages all console related functions including loading and saving related objects. The functions are for the most part simply copy/pasted from the generated service component code, except I have revised the existing methods slightly and added a new standard create method. The create method creates a new instance of an object and populates it with the passed values. The created object can be used to pass to the save method but the create() method is also referenced by my get() and delete() methods. Here are the methods for game related functionality from consoleService.cfc:

<cffunction name="createGame" access="public" output="false" returntype="com.xbox.games.game">
<cfargument name="gameID" type="uuid" required="true" />
<cfargument name="gameName" type="String" required="false" />
<cfargument name="specialEdition" type="Boolean" required="false" />
<cfset var game = createObject("component","com.xbox.games.game").init(argumentCollection=arguments) />
<cfreturn game />
</cffunction>
<cffunction name="getGame" access="public" output="false" returntype="com.xbox.games.game">
<cfargument name="gameID" type="uuid" required="true" />
<cfset var game = createGame(argumentCollection=arguments) />
<cfset variables.gameDAO.read(game) />
<cfreturn game />
</cffunction>
<cffunction name="getGames" access="public" output="false" returntype="array">
<cfargument name="gameID" type="uuid" required="false" />
<cfargument name="gameName" type="String" required="false" />
<cfargument name="specialEdition" type="Boolean" required="false" />
<cfargument name="consoleID" type="uuid" required="false" />
<cfreturn variables.gameGateway.getByAttributes(argumentCollection=arguments) />
</cffunction>
<cffunction name="saveGame" access="public" output="false" returntype="boolean">
<cfargument name="game" type="com.xbox.games.game" required="true" />

<cfreturn variables.gameDAO.save(arguments.game) />
</cffunction>
<cffunction name="deleteGame" access="public" output="false" returntype="boolean">
<cfargument name="gameID" type="uuid" required="true" />
<cfset var game = createGame(argumentCollection=arguments) />
<cfreturn variables.gameDAO.delete(game) />
</cffunction>

You will notice I am calling the game DAO and game Gateway components. These were inserted into the service on initialization. This was done by hand, since in this example I am not using ColdSpring (look for this in a possible part 2). Taking a look at the code injecting all the services dependencies and their respective dependencies from index.cfm, you can easily see how in a real, full-size application, managing dependencies can become a real hassle - and therefore why ColdSpring can make your application easier to build and maintain.

<!--- this service should be a singleton and in application scope in your application. this would be in onApplictionStart() in application.cfc --->
<cfset dsn = "xbox" />
<!--- create our dependencies to pass in...can you see why ColdSpring is a good idea? --->
<cfset consoleDAO = createObject("component","com.xbox.consoleDAO").init(dsn) />
<cfset consoleGateway = createObject("component","com.xbox.consoleGateway").init(dsn) />
<cfset controlDAO = createObject("component","com.xbox.controls.controlDAO").init(dsn) />
<cfset controlGateway = createObject("component","com.xbox.controls.controlGateway").init(dsn) />
<cfset accessoryDAO = createObject("component","com.xbox.accessories.accessoryDAO").init(dsn) />
<cfset accessoryGateway = createObject("component","com.xbox.accessories.accessoryGateway").init(dsn) />
<cfset gameDAO = createObject("component","com.xbox.games.gameDAO").init(dsn) />
<cfset gameGateway = createObject("component","com.xbox.games.gameGateway").init(dsn) />

<cfset consoleService = createObject("component","com.xbox.consoleService").init(consoleDAO,consoleGateway,controlDAO,controlGateway,accessoryDAO,accessoryGateway,gameDAO,gameGateway) />

Now that this is done, the following code will work:

<!--- create a game --->
<cfset myGame1 = consoleService.createGame(createUUID(),"Gears of War",false) />
<cfset consoleService.saveGame(myGame1) />

Saving Dependent Items in the DAO
By this point, we can create a console and save it to the database, as well as create any of the dependent objects and save them. Still, if I were to create a game and add it to my console and then save the console, the relationship would not be persisted because our consoleDAO only knows how to save the basic console properties. To save the related objects, I have created new methods for inserting records into the linking tables. For example, the following code from consoleDAO.cfc loops through the array of accessories and saves them:

<cffunction name="saveAccessories" access="private" output="false" returntype="void">
<cfargument name="console" type="console" required="true" />
&nbsp; <cfset var i = 0 />
<cfset var clearAccessoryRel = "" />
<cfset var saveAccessoryRel = "" />
<cfset var accessories = arguments.console.getAccessories() />
<!--- first clear existing relationships --->
<cfquery name="clearAccessoryRel" datasource="#variables.dsn#">
DELETE FROM relconsoleaccessories WHERE consoleID = <cfqueryparam value="#arguments.console.getconsoleID()#" CFSQLType="cf_sql_char" />
</cfquery>
<cfif arrayLen(accessories)>
<cfloop from="1" to="#arrayLen(accessories)#" index="i">
<cfquery name="saveAccessoryRel" datasource="#variables.dsn#">
INSERT INTO relconsoleaccessories(consoleid,accessoryid) VALUES (<cfqueryparam value="#arguments.console.getconsoleID()#" CFSQLType="cf_sql_char" />
, <cfqueryparam value="#accessories[i].getAccessoryID()#" CFSQLType="cf_sql_char" />
) </cfquery>
</cfloop>
</cfif>
</cffunction>

We simply call this method after the insert query in the create() method or the update query in the update() method with <cfset saveAccessories(arguments.console) />. Now saving a console will persist any relationships that exist within the composite object (it is important to point out that if you add a new game, for instance, you would need to save the game before saving a console that contains the game).

Restoring Persisted Relationships on Read
Now that we can save our objects and have the relationships persisted, we need to be able to retrieve that object and restore the relationships. This leads me to another change I made to the existing generated code from Illudium, and that is I modified the gateway component to return an array of objects rather than a query directly (actually I allow it to do both, as you can see below in my gameGateway.cfc):

<cffunction name="getByAttributesQuery" access="public" output="false" returntype="query">
<cfargument name="gameID" type="uuid" required="false" />
<cfargument name="consoleID" type="uuid" required="false" />
<cfargument name="gameName" type="String" required="false" />
<cfargument name="specialEdition" type="Boolean" required="false" />
<cfargument name="orderby" type="string" required="false" />
<cfset var qList = "" />
<cfquery name="qList" datasource="#variables.dsn#">
SELECT g.gameID, g.gameName, g.specialEdition FROM game g INNER JOIN relconsolegames cg on g.gameID = cg.gameID WHERE 0=0 <cfif structKeyExists(arguments,"gameID") and len(arguments.gameID)>
AND gameID = <cfqueryparam value="#arguments.gameID#" CFSQLType="cf_sql_char" />
</cfif>
<cfif structKeyExists(arguments,"gameName") and len(arguments.gameName)>
AND gameName = <cfqueryparam value="#arguments.gameName#" CFSQLType="cf_sql_varchar" />
</cfif>
<cfif structKeyExists(arguments,"specialEdition") and len(arguments.specialEdition)>
AND specialEdition = <cfqueryparam value="#arguments.specialEdition#" CFSQLType="cf_sql_tinyint" />
</cfif>
<cfif structKeyExists(arguments,"consoleID") and len(arguments.consoleID)>
AND cg.consoleID = <cfqueryparam value="#arguments.consoleID#" CFSQLType="cf_sql_char" />
</cfif>
<cfif structKeyExists(arguments, "orderby") and len(arguments.orderBy)>
ORDER BY #arguments.orderby# </cfif>
</cfquery>
<cfreturn qList />
</cffunction>
<cffunction name="getByAttributes" access="public" output="false" returntype="array">
<cfargument name="gameID" type="uuid" required="false" />
<cfargument name="consoleID" type="uuid" required="false" />
<cfargument name="gameName" type="String" required="false" />
<cfargument name="specialEdition" type="Boolean" required="false" />
<cfargument name="orderby" type="string" required="false" />
<cfset var qList = getByAttributesQuery(argumentCollection=arguments) />
<cfset var arrObjects = arrayNew(1) />
<cfset var tmpObj = "" />
<cfset var i = 0 />
<cfloop from="1" to="#qList.recordCount#" index="i">
<cfset tmpObj = createObject("component","com.xbox.games.game").init(argumentCollection=queryRowToStruct(qList,i)) />
<cfset arrayAppend(arrObjects,tmpObj) />
</cfloop>
<cfreturn arrObjects />
</cffunction>

The reason I do this is twofold: 1) it simplifies passing data to the relationships in the case of our composite object and 2) combined with the cfproperty tags in our beans, this code will work well as a back-end to a Flex application using the ColdFusion integration without modification. However, for those instances when a query object is preferable, simply call getByAttributesQuery() method.

Now that we are returning the gateway data as arrays of objects, passing them into the console object becomes extremely simple as can be seen in the following lines from the getConsole() method of my consoleService.cfc:

<cfset console.setAccessories(getAccessories(consoleID=console.getConsoleID())) />
<cfset console.setGames(getGames(consoleID=console.getConsoleID())) />
<cfset console.setControls(getControls(consoleID=console.getConsoleID())) />

A couple of important notes here: 1) my getConsoles() method is returning an array of console objects but is currently not building these as composite objects (i.e. they only have the data properties and not the controls, accessories or games); 2) as this example is pretty basic I don't get into the situation whereby your objects is composed of an object that is itself composed of additional objects (for example, if my game control was composed of a headset object rather than just having a headset property).

Now that this is done, the code <cfset myLoadedConsole = consoleService.getConsole(myConsole.getConsoleID()) /> will return a console with its controls, games and accessories all en tact.

Conclusion
To see all of this code in "action" simply run the index.cfm file included in the attachment (you must create the MySql datasource first obviously). This code will run through creating accessories, controls and games, adding these to a console which is saved and then retrieved from the database. It's nothing very exciting but it is, I hope, an easy example to follow if OO and composition are new to you.

Hopefully this helps those of you out there who are learning the concepts of object-oriented application design but unclear on the implementation. Please add any feedback to the comments so that I can improve the examples as this is intended to be the first post in a series. Going forward, I would like to show the same example using ColdSpring, and then ColdSpring and Transfer.

Download the attachment.

Comments

jim collins Great article Brian. I look forward to seeing the ColdSprnig and Transfer versions!

Posted By jim collins / Posted on 06/01/2007 at 2:36 PM


Rob Great article. What would you do if the objects used in composition have their own Service, Gateway, and DAO objects? Would you then compose the sub-object's DAO or Service object into the base object's DAO for read and save operations?

Posted By Rob / Posted on 06/01/2007 at 5:26 PM


Brian Rinaldi Each of my objects does have its own DAO and Gateway and they don't need to know about each other - only the service does. As for the service, while it can be done, I think the service should span more than just a single object (I know that is not how the generator generates it, but that is because the generator is not generating applications, just some code which is often more useful as snippets). Anyway, you can have a service be dependent on another service by passing it in as you do the various DAOs and gateways. In fact, that type of dependency is exactly what ColdSpring is about.

Posted By Brian Rinaldi / Posted on 06/01/2007 at 6:40 PM


Tony Garcia Thanks, Brian! As you know, I've been trying to get my feet wet with this OO stuff and it just so happens I've been trying to grasp how to implement composition for a project I'm working on, so this post comes at a great time for me.
One comment: you mention what a pain it can be to first instantiate all your dependencies and then pass them into your service object as arguments. What I've been doing is running setters in my service object's init method which instantiates all of the DAOs and Gateways within the service object itself. So all you have to do is createObject("component","com.myServiceObject").init(mydsn) and then all of the DAOs and Gateways are just automatically created (within the variables scope of the service object). I think I saw this in some example code from Matt Woodward. Are there any disadvantages to doing it this way as opposed to your method? Of course, as you point out, another option is ColdSpring. But I don't think I'm at that point just yet.

Posted By Tony Garcia / Posted on 06/04/2007 at 10:37 PM


Brian Rinaldi Tony, this is an acceptable solution, though the net result is the same - i.e. you still have a bunch of lines calling the setter methods in your service, and I have a bunch in a different place. ColdSpring is actually such a simple and straightforward solution to this issue that once you understand the problem, you shouldn't really have any issue implementing it. Anyway, that is supposed to be my next example for the next post ;)

Posted By Brian Rinaldi / Posted on 06/05/2007 at 12:34 AM


Dave Farr Brian - I am working with a collegue who is trying to learn OO. This article is very helpful. Would really be great if you have the time to show how ColdSpring changes the code. Having an simple example comparing the two methods would be well received.

Posted By Dave Farr / Posted on 06/05/2007 at 10:47 AM


Brian Rinaldi FYI, the ColdSpring version of this has now been posted at http://www.remotesynthesis.com/blog/index.cfm/2007/6/5/Objects-and-Composition-in-CFCs--Part-2-ColdSpring

Posted By Brian Rinaldi / Posted on 06/05/2007 at 3:59 PM


Hatem Jaber Brian, as always I enjoy your articles and insight into the world of ColdFusion. Here is a link to an article that I just read that I think will help compliment yours:

http://www.adobe.com/devnet/coldfusion/articles/supsub.html

Posted By Hatem Jaber / Posted on 10/09/2007 at 9:41 AM


Lola LB Just wanted to let you know that the code in this post is coming out unformatted . . .

Posted By Lola LB / Posted on 07/01/2008 at 6:50 AM


John Haigh Hi,

When I run this I get the following error when loading index.cfm:

The value returned from the init function is not of type com.xbox.consoleDAO.
If the component name is specified as a return type, its possible that a definition file for the component cannot be found or is not accessible.

The error occurred in C:\cfdev5\xbox\index.cfm: line 7

5 : <cfset dsn = "xbox" />
6 : <!--- create our dependencies to pass in...can you see why ColdSpring is a good idea? --->
7 :    <cfset consoleDAO = createObject("component","com.xbox.consoleDAO").init(dsn) />
8 :    <cfset consoleGateway = createObject("component","com.xbox.consoleGateway").init(dsn) />
9 :    <cfset controlDAO = createObject("component","com.xbox.controls.controlDAO").init(dsn) />

Posted By John Haigh / Posted on 09/26/2008 at 12:15 PM


Brian Rinaldi @John - I haven't checked the download but you can get the full code for this and the follow up posts at http://code.google.com/p/remotesynthesis (in the SVN trunk look under Xbox OO Examples)

Posted By Brian Rinaldi / Posted on 09/26/2008 at 12:20 PM


John Haigh Hi,

When I run this I get the following error when loading index.cfm:

The value returned from the init function is not of type com.xbox.consoleDAO.
If the component name is specified as a return type, its possible that a definition file for the component cannot be found or is not accessible.

The error occurred in C:\cfdev5\xbox\index.cfm: line 7

5 : <cfset dsn = "xbox" />
6 : <!--- create our dependencies to pass in...can you see why ColdSpring is a good idea? --->
7 :    <cfset consoleDAO = createObject("component","com.xbox.consoleDAO").init(dsn) />
8 :    <cfset consoleGateway = createObject("component","com.xbox.consoleGateway").init(dsn) />
9 :    <cfset controlDAO = createObject("component","com.xbox.controls.controlDAO").init(dsn) />

Posted By John Haigh / Posted on 09/29/2008 at 9:43 AM


Brian Rinaldi @John - I can't see what's wrong with your setup. Probably you don't have it mapped. If your site root is cfdev5 then your path would be xbox.com.xbox and my code won't work out of the box, so you'd have to add a mapping.

Beyond that I am not sure what you are asking of me by posting the identical comment twice. The code works, but you are going to have to do some examination on your end.

Posted By Brian Rinaldi / Posted on 09/29/2008 at 10:03 AM


Jesse I'm getting the same error as John cept I have your code running in my IIS site root so http://localhost/ points to the xbox dir which holds the index file and the com dir. The only thing I can think that is messing me up is my coldfusion internal web server web root is pointing else where. I usually dev apps locally in my cf/wwwroot/appfolder and I did not feel like replacing all the com.xbox references and a mapping to com would break my other apps.

Posted By Jesse / Posted on 04/21/2009 at 7:46 PM


jesse disregard... I knew I had it set up right, just had to restart cf services... switching from the cf webserver to IIS cornfuzed it.

Posted By jesse / Posted on 04/21/2009 at 7:52 PM


Write your comment



(it will not be displayed)





About

My name is Brian Rinaldi and I am the Web Community Manager for Flash Platform at Adobe. I am a regular blogger, speaker and author. I also founded RIA Unleashed conference in Boston. The views expressed on this site are my own & not those of my employer.