Objects and Composition Part 3 - Using Transfer
Posted on Jun 20, 2007
In part 1 of this series, we learned how to use the code generator to help you get a head start on building your object-oriented code from scratch, including handling object composition. Part 2 showed how you could simplify managing dependencies using ColdSpring, however your components were still all maintained by hand. While, the code generator can give you a good head start, building and maintaining these components and managing composition can be a lot of work - especially when it comes to mapping these objects back to a relational database that doesn't understand composition but rather foreign keys.
Solving this problem is the core purpose of object-relational mapping (i.e. ORM) applications, the most well known probably being Hibernate for Java. A popular ORM for ColdFusion is Transfer created by Mark Mandel. While Transfer makes application development easy by eliminating a lot of the repetitive code that you have to build and maintain, the Illudium PU-36 code generator includes Transfer templates out-of-the-box to make that process even easier. This article will show you how to take the example objects from the previous article (i.e. our XBox console) and use Transfer to build the same - actually improved - functionality in less time.
Since this is the last article planned in this series for now, the download attachment includes all three examples to allow you to more easily see the progression from no framework, to ColdSpring integration, and finally to ColdSpring and Transfer integration. Also, this article resulted in a much tighter Transfer templates for the generator, which are available in the latest build.Installing Transfer
Installing Transfer is a simple process. First, go download the code from the Transfer site, unzip it and place the "transfer" folder in the root of your web site. Just as with ColdSpring, depending on your local development set up you may require a mapping to ensure that the framework can be found at "/transfer", however in a typical multi-site setup just putting it under the root will work (and, yes, this also works in shared hosting environments).
My configuration folder has three XML files included, two for Transfer and one for ColdSpring, which we will use to make integrating Transfer cleaner and easier.
The Datasource XML
This simple XML file defines the datasource that Transfer should use. Since my datasource does not require a username and pass, all I need to do is supply the name:
<datasource xsi:noNamespaceSchemaLocation="/transfer/resources/xsd/datasource.xsd" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> <name>xbox</name> <username></username> <password></password> </datasource>
The Transfer XML
The Transfer XML is where you define all your "business objects" that Transfer will handle, as well as their properties and relationships. The business objects are grouped into packages for purely organizational purposes. Each objects properties should represent the various table columns associated with that item within your RDBMS - this is one place where the Illudium code generator can help. The Transfer templates for Illudium will generate, among other things, a Transfer XML snippet which you can copy and paste into your transfer.xml file, saving you the time of having to write out the object, table and column details. It does present each within its own package, which you will want to manually reorganize within your application. For example, here is the XML snippet for my "game" object:
<object name="game" table="game" decorator="tf.com.xbox.games.game"> <id name="gameID" type="uuid" /> <property name="gameName" type="String" column="gameName" /> <property name="specialEdition" type="Boolean" column="specialEdition" /> </object>
You will notice that I have supplied a decorator for my game object - in fact for each of the objects. The Transfer templates for Illudium will auto-generate this decorator component for you with the validate method included. Without a decorator, Transfer returns a generic Transfer object with the methods for your specific object injected. A decorator simply "wraps" the Transfer object in a preexisting component where you can add to or override the existing methods it inherits.
The Transfer XML is also where you will manage your objects relationship to other objects; you can have OneToMany, ManyToOne or ManyToMany relationships. In this case, my "console" object has many-to-many relationships with accessories, controls and games. You can define this relationship to return a structure or an array. I have chosen to return an array ordered on the id for each, as you can see in the following sample code for the accessories relationship:
<manytomany name="accessories" table="relconsoleaccessories"> <link to="console.console" column="consoleID"/> <link to="console.accessory" column="accessoryID"/> <collection type="array"> <key order="accessoryID"/> </collection> </manytomany>
Transfer has really great documentation that covers the specifics of these configurations in good detail, however if you examine the Transfer XML in the sample code attached, you will see that it is fairly self-explanatory and straightforward.
The ColdSpring XML
Now that we have decided to use Transfer, obviously our service layer (in this case a single CFC) becomes dependent on having the Transfer object passed in (rather than the DAO dependencies from the prior examples). In addition, our generated Table Gateway components use a relatively new feature in Transfer called TQL - the Transfer Query Language. TQL not only allows us to easily create queries that will work on any of Transfer's supported RDBMS's, but it also allows us to leverage the column and relationship data we have already supplied within the Transfer XML. Here is an example query function from the gameGateway, you will notice I don't supply column names or any specifics on how the relationship is managed:
<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" default="game.gameID" /> <cfset var qList = "" /> <cfset var tQuery = "" /> <cfsavecontent variable="qList"> <cfoutput> FROM console.game AS game JOIN console.console AS console WHERE game.gameID IS NOT NULL <cfif structKeyExists(arguments,"gameID") and len(arguments.gameID)> AND game.gameID = :gameID </cfif> <cfif structKeyExists(arguments,"gameName") and len(arguments.gameName)> AND game.gameName = :gameName </cfif> <cfif structKeyExists(arguments,"specialEdition") and len(arguments.specialEdition)> AND game.specialEdition = :specialEdition </cfif> <cfif structKeyExists(arguments,"consoleID") and len(arguments.consoleID)> AND console.consoleID = :consoleID </cfif> <cfif structKeyExists(arguments, "orderby") and len(arguments.orderBy)> ORDER BY #arguments.orderby# </cfif> </cfoutput> </cfsavecontent> <cfset tQuery = variables.transfer.createQuery(qList) /> <cfif structKeyExists(arguments,"gameID") and len(arguments.gameID)> <cfset tQuery.setParam("gameID",arguments.gameID) /> </cfif> <cfif structKeyExists(arguments,"gameName") and len(arguments.gameName)> <cfset tQuery.setParam("gameName",arguments.gameName) /> </cfif> <cfif structKeyExists(arguments,"specialEdition") and len(arguments.specialEdition)> <cfset tQuery.setParam("specialEdition",arguments.specialEdition) /> </cfif> <cfif structKeyExists(arguments,"consoleID") and len(arguments.consoleID)> <cfset tQuery.setParam("consoleID",arguments.consoleID) /> </cfif> <cfreturn variables.transfer.listByQuery(tQuery) /> </cffunction>
Of course, now that my gateway components use TQL, they also become dependent on having Transfer passed to them. Thankfully, ColdSpring will manage all of this for us, and the code generator will generate all the code we need to do this. It also uses a method discussed by Brian Kotek and Sean Corfield to utilize the factory method attribute within ColdSpring so that there is no need to call the Transfer.getTransfer() method within each of these components. Here is my new ColdSpring XML configuration for the XBox "console" object:
<!-- Transfer --> <bean id="transfer" class="transfer.TransferFactory"> <constructor-arg name="datasourcePath"><value>/tf/config/datasource.xml</value></constructor-arg> <constructor-arg name="configPath"><value>/tf/config/transfer.xml</value></constructor-arg> <constructor-arg name="definitionPath"><value>/tf/com/transfer</value></constructor-arg> </bean> <!-- console --> <bean id="consoleGateway" class="tf.com.xbox.consoleGateway"> <constructor-arg name="transfer"> <bean id="transfer" factory-bean="transfer" factory-method="getTransfer" /> </constructor-arg> </bean> <bean id="consoleService" class="tf.com.xbox.consoleService"> <constructor-arg name="transfer"> <bean id="transfer" factory-bean="transfer" factory-method="getTransfer" /> </constructor-arg> <constructor-arg name="consoleGateway"> <ref bean="consoleGateway"/> </constructor-arg> <constructor-arg name="controlGateway"> <ref bean="controlGateway"/> </constructor-arg> <constructor-arg name="accessoryGateway"> <ref bean="accessoryGateway"/> </constructor-arg> <constructor-arg name="gameGateway"> <ref bean="gameGateway"/> </constructor-arg> </bean>
Generating the Service, Decorators and Gateways
Now that you have finished all the configuration (which wasn't that hard to begin with now was it?), it's time to write your components. Except, as before, why write them by hand when you can generate them? The next step would then be to walk through the console, games, controls and accessories tables and generate the decorators and gateways. As before, I have combined the service methods generated for each into a single service component.
Now, if you remember my discussion of behaviors in your objects from part 1, you might remember how I mentioned that objects with properties and no behaviors can lead towards the Anemic Domain Model antipattern. Thankfully, using our decorators, Transfer let's us handle this elegantly. For example, below is my decorator for my "console" object with the loadGame(), unloadGame() and getLoadedGame() methods from the prior examples all mixed-in:
<cfcomponent displayname="console" output="false" extends="transfer.com.TransferDecorator"> <cfproperty name="consoleID" type="String" default="" /> <cfproperty name="type" type="String" default="" /> <cfproperty name="storage" type="Numeric" default="" /> <cfproperty name="accessories" type="Array" /> <cfproperty name="controls" type="Array" /> <cfproperty name="games" type="Array" /> <cffunction name="loadGame" access="public" returntype="void" output="false"> <cfargument name="gameName" type="string" required="true" /> <cfset var i = 0 /> <cfset var games = getGamesArray() /> <cfloop from="1" to="#arrayLen(games)#" index="i"> <cfif games[i].getGameName() eq arguments.gameName> <cfset variables.loadedGame = i /> <cfbreak /> </cfif> </cfloop> </cffunction> <cffunction name="unloadGame" access="public" returntype="void" output="false"> <cfset variables.loadedGame = 0 /> </cffunction> <cffunction name="isGameLoaded" access="public" returntype="boolean" output="false"> <cfparam name="variables.loadedGame" default="0" /> <cfreturn variables.loadedGame GT 0 /> </cffunction> <cffunction name="getLoadedGame" access="public" returntype="tf.com.xbox.games.game" output="false"> <cfset var games = getGamesArray() /> <cfif not isGameLoaded()> <cfthrow errorcode="tf.com.xbox.console.noGameLoaded" /> </cfif> <cfreturn games[variables.loadedGame] /> </cffunction> <P>
The test page for my sample application (index.cfm) did require some adjustment. First of all, I no longer needed to pass along the DSN information in a properties structure to ColdSpring because that is handled by the Transfer configuration. Other than that, the only required change to my code was that getting the component objects (i.e. the objects that comprise my "console" composite object) from things like getControls() to getControlsArray() to conform to Transfer's standard method naming - this could have easily been handled by a simple change in the decorators in fact if I didn't want to change my interface. Other that, my code remains the same for saving and displaying the varying objects.
Transfer is one of those projects that impresses you more and more every time you use it in terms of the overall power it offers and the ease of configuration and coding. This job is made even easier, in my opinion, with the Illudium code generator because when you use both Transfer and Illudium, your entire model can be built with minimal hand-coding required. At the very least, the monotonous tasks become both quick and easy (and therefore no longer monotonous :).
This is the last in this three part series, and I really hope it was helpful to some people who were not intimidated by the length of the articles - I do know that seemed to limit its audience as this isn't an easy topic. I know I feel as though I have written a book between the three. Nonetheless, this was a great exercise even for me and in the end, I think, it resulted in much improved templates for the generator. In addition, expect to see the sample application I built here included as the oft requested example for using my Illudium project.
Posted By Peter Bell / Posted on 06/20/2007 at 7:39 PM
What I would really like to know is how people are doing validation with Transfer. My biggest gripe with Transfer is that it doesn't support nulls, so it make it impossible to stuff say form data into a Transfer and call a validation method.
Posted By Tony Petruzzi / Posted on 06/21/2007 at 12:55 AM
Posted By Brian Rinaldi / Posted on 06/21/2007 at 1:26 AM
Thanks for this article, I found it extremely helpful today. I already had some of my files setup from a previous go at doing ColdSpring/Transfer/Model-Glue but I didn't really not what I was doing. I went through this article diligently paragraph by paragraph. At the end of each paragraph I checked your code, updated my code accordingly - and at the end of the day everything worked!
All this Gateway, Service, Model, ColdSpring stuff isn't actually that difficult but there is a significant initial hurdle to get over. Your article helped lower that hurdle.
Now back to re-read all Ray Camden's article on Model-Glue.
Posted By William Fisk / Posted on 06/29/2007 at 7:15 PM
Posted By todd sharp / Posted on 07/02/2007 at 7:11 PM
Posted By nick tong / Posted on 08/04/2007 at 1:14 PM
I followed your series and am moving to Transfer. It looks awesome, but there are a couple of holes in my knowledge and I can't find answers (or at least they aren't clear). First, in the <constructor-arg name="definitionPath"><value>/tf/com/transfer</value></constructor-arg>
what is the definithionPath referring to?
Second, is there something that needs to be done to create the transfer service like we do Coldspring (BeanFactory)?
Hope you can help. Thanks!
Posted By Judah Miles / Posted on 10/02/2007 at 11:10 PM
Posted By Brian Rinaldi / Posted on 10/03/2007 at 5:21 AM
Thanks I figured definitions out rereading the Transfer documentation. I'll try again on the other question. I have my config files I've used the generator and have all the files where they are supposed to be. When I rerun my application instantiation. Nothing shows up in the definitions directory. When I run a test nothing comes back. I am using Coldspring and before adding transfer I was getting data back. Is there something I am missing in wiring Coldspring and Transfer together. Do you have a the sample app you put together that you could point me to so I can double check? Thanks!
Posted By Judah / Posted on 10/08/2007 at 7:44 AM
If there was a problem you would generally get an error. Make sure that you have correctly stated the location where Transfer will put the generated files. Other than that it should work out of the box. You can download the sample attached to this post for a sample app if you like. If you have additional issues, feel free to ping me offline via email.
Posted By Brian Rinaldi / Posted on 10/11/2007 at 5:26 AM
I just started using illidium and I looked at the files created for a single table. I created files using the default template and transfer template.
CRUD SQL statements are missing in the transfer output files. I suppose the SQL operations is handled by transfer?
Thank you very much for the help :)
Posted By jarthel / Posted on 04/10/2009 at 7:27 PM