Objects and Composition in CFCs
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.
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.
http://www.adobe.com/devnet/coldfusion/articles/supsub.html

