Building an Application with Transfer ORM
Configuring Transfer
Let's look at the example config file for my open source List. The items on the list I call "resources," each of which have a many-to-many relationship with categories and a many-to-many relationship with authors (note: Transfer also uses a datasource config which is very simple and self-explanatory so I am not covering it here). Here is the XML:
<package name="resource">
<object name="Resource" table="opensourceresource">
<id name="resourceID" type="UUID"/>
<property name="title" type="string" column="title"/>
<property name="href" type="string" column="href"/>
<property name="description" type="string" column="description"/>
<property name="dateAdded" type="date" column="dateAdded"/>
<property name="license" type="string" column="license"/>
<property name="freeButNotOS" type="numeric" column="freeButNotOS"/>
<property name="searchTerm" type="string" column="searchTerm"/>
<property name="clickCount" type="numeric" column="clickCount"/>
<manytomany name="category" table="opensourceresourcecategories">
<link to="resource.Resource" column="resourceID"/>
<link to="resource.Category" column="categoryID"/>
<collection type="struct">
<key property="categoryID"/>
</collection>
</manytomany>
<manytomany name="author" table="opensourceresourceauthor">
<link to="resource.Resource" column="resourceID"/>
<link to="resource.Author" column="authorID"/>
<collection type="struct">
<key property="authorID"/>
</collection>
</manytomany>
</object>
<object name="Category" table="opensourcecategory">
<id name="categoryID" type="UUID"/>
<property name="category" type="string" column="category"/>
</object>
<object name="Author" table="opensourceauthor">
<id name="authorID" type="UUID"/>
<property name="authorName" type="string" column="authorName"/>
<property name="blogURL" type="string" column="blogURL"/>
<property name="blogRSS" type="string" column="blogRSS"/>
<property name="email" type="string" column="email"/>
</object>
</package>
One thing you will notice is that you explicitly define the columns/properties in a Transfer config. This can be a little tedious on a table with many columns, but thank goodness some bright individual added generation of Transfer config files to his code generator. (as a side note, the column and name are not both required since @column defaults to the value of @name - thanks Mark).
Transfer does actually generate code, but it isn't intended (I believe) to be usable. If you need to customize your generated Transfer objects, the framework offers a means to inject functions into the objects via the config. I have not used or needed this functionality for this simple application, so I recommend referring to the documentation if it is something you require. Transfer also has a sophisticated caching mechanism which I also left at the default.
Managing the TransferFactory with ColdSpring
Now that you have Transfer up and running, your service layer needs to have access to it, and the simplest way to handle this sort of dependency is via ColdSpring. I have ColdSpring create the TransferFactory singleton and inject that into all of the services that need it as in this example:
<bean id="transfer" class="transfer.transferFactory">
<constructor-arg name="datasourcePath">
<value>/config/datasource.xml</value>
</constructor-arg>
<constructor-arg name="configPath">
<value>/config/transfer.xml</value>
</constructor-arg>
<constructor-arg name="definitionPath">
<value>/model/transfer</value>
</constructor-arg>
</bean>
<bean id="resourceService" class="model.resource.resourceService">
<constructor-arg name="resourceGateway">
<ref bean="resourceGateway"/>
</constructor-arg>
<constructor-arg name="transfer">
<ref bean="transfer"/>
</constructor-arg>
</bean>
Notice that I am also injecting a gateway into my service. Transfer does include methods for handling standard gateway functions, but I have realized two things about myself recently: 1) I don't hate writing SQL, in fact, I kinda like it; 2) generated gateway code rarely ever handles the necessary complexity of a gateway query. Therefore, in this case, my gateway methods are custom written and return simple a ColdFusion query object.
Obviously now that you have ColdSpring injecting Transfer via the constructor, you need to set an argument in the init() of your service to accept it and store it in the variables scope.
Loading an Object
Once Transfer is set, this code will get me any "resource" with its related category and author information (or an empty resource object if a resource with the supplied ID doesn't exist):
<cfset var resource = variables.transfer.get("resource.Resource",arguments.resourceID) />
One behavior that I noticed was that if I send it an ID for an item that doesn't exist, it will reset the id (a uuid in this case, so it is set to uuid format but all zeros). This is a bit of a pain in my opinion since I use the same code to add as to delete (or to pre-populate a form with an empty object). I got around that by using the isPersisted() method for the bean, which tells me if this item was stored in the database (there is also an isDirty() method which can tell if it has been changed) as follows (this example is for getting an author):
<cffunction name="getAuthor" access="public" output="false" returntype="any">
<cfargument name="authorID" type="uuid" required="false" default="#createUUID()#" />
<cfset var author = variables.transfer.get("resource.Author",arguments.authorID) />
<cfif not author.getIsPersisted()>
<cfset author.setAuthorID(arguments.authorID) />
</cfif>
<cfreturn author />
</cffunction>
Saving an Object
In the case of saving a resource, my code was slightly more complicated. First, the form for adding a resource allowed you to select from existing categories or add new categories as a comma-delimited list (much like adding a blog post in Ray's BlogCFC). You could also choose one or many authors from a predefined list of authors. The following is the full code with of my saveresource() method. If you follow the code and comments it should be fairly obvious what is going on. One key thing to notice, as with most ORMs, Transfer automatically inserts and deletes the necessary records into my relationship table when I save the parent object.
<cffunction name="saveresource" access="public" output="false" returntype="boolean">
<cfargument name="resourceID" type="uuid" required="true" />
<cfargument name="title" type="string" required="true" />
<cfargument name="href" type="string" required="true" />
<cfargument name="description" type="string" required="true" />
<cfargument name="license" type="string" required="false" default="" />
<cfargument name="searchTerm" type="string" required="false" default="" />
<cfargument name="freeButNotOS" type="boolean" required="false" default="false" />
<cfargument name="categories" type="string" required="false" default="" />
<cfargument name="newCategories" type="string" required="false" default="" />
<cfargument name="authors" type="string" required="false" default="" />
<cfset var resource = variables.transfer.get("resource.Resource",arguments.resourceID) />
<cfset var i = 0 />
<cfset var category = "" />
<cfif not resource.getIsPersisted()>
<cfset resource.setResourceID(arguments.resourceID) />
</cfif>
<!--- save resource --->
<cfset resource.setTitle(arguments.title) />
<cfset resource.setHref(arguments.href) />
<cfset resource.setDescription(arguments.description) />
<cfset resource.setFreeButNotOS(arguments.freeButNotOS) />
<cfif len(arguments.license)>
<cfset resource.setLicense(arguments.license) />
</cfif>
<cfif len(arguments.searchTerm)>
<cfset resource.setSearchTerm(arguments.searchTerm) />
</cfif>
<cfif not len(resource.getDateAdded())>
<cfset resource.setDateAdded(now()) />
</cfif>
<!--- first, just clear all the categories --->
<cfset resource.clearCategory() />
<!--- if there are new categories, add them --->
<cfif listLen(arguments.newCategories)>
<cfloop from="1" to="#listLen(arguments.newCategories)#" index="i">
<cfset category = variables.transfer.new("resource.Category") />
<cfset category.setCategoryID(createUUID()) />
<cfset category.setCategory(trim(listGetAt(arguments.newCategories,i))) />
<cfset variables.transfer.save(category) />
<cfset resource.addCategory(category) />
</cfloop>
</cfif>
<!--- go through existing categories list and add --->
<cfif listLen(arguments.categories)>
<cfloop from="1" to="#listLen(arguments.categories)#" index="i">
<cfset category = variables.transfer.get("resource.Category",trim(listGetAt(arguments.categories,i))) />
<cfset resource.addCategory(category) />
</cfloop>
</cfif>
<!--- first, just clear all the authors --->
<cfset resource.clearAuthor() />
<!--- go through existing categories list and add --->
<cfif listLen(arguments.authors)>
<cfloop from="1" to="#listLen(arguments.authors)#" index="i">
<cfset author = variables.transfer.get("resource.Author",trim(listGetAt(arguments.authors,i))) />
<cfset resource.addAuthor(author) />
</cfloop>
</cfif>
<cfset variables.transfer.save(resource) />
<cfreturn true>
</cffunction>
You may notice that much like a "standard" data access object (DAO), you pass Transfer the populated bean as transfer.save(bean) rather than call bean.save(). This is both a subtle but distinct difference, and in my opinion, keeps your code following a similar structure as it would if you choose to remove Transfer support for some reason.
Some Notes on Relationships and Creating the Form
If you notice in the config, I created the many-to-many relationships as structures using a key of the ID. I could have defined those as arrays with a specified sort as well. I chose to use a structure to simplify the form logic as follows:
1) Get the structure containing the resource categories keyed on the categoryID;
<cfset resourceCategoryStruct = resource.getCategoryStruct() />
2) Set the item in the select list as selected if the id exists in the structure;
<option value="#qryCategories.categoryID#" <cfif structKeyExists(resourceCategoryStruct,qryCategories.categoryID)>selected="true"</cfif>>#qryCategories.category#</option>
Deleting an Object
Now let's cover how you would handle the delete. It functions much like the save, as you pass the populated bean you would like to delete.
<cffunction name="deleteresource" access="public" output="false" returntype="boolean">
<cfargument name="resourceID" type="uuid" required="true" />
<cfset var resource = variables.transfer.get("resource.Resource",arguments.resourceID) />
<cfset variables.transfer.delete(resource) />
<cfreturn true />
</cffunction>
As you can see this is all pretty straightforward. You may ask, I thought you said you built this application with Mach II? Yes, I did. Well what does any of this code have to do with Mach II? Nothing at all, and that is the beauty of using a framework with a service layer: you could use this code with any framework or no framework at all. There really isn't anything specific to Transfer in my Mach II listeners, and there is nothing specifically Mach II related in my service components.
A Word on Performance
So how well does Transfer perform? Well, I don't see any performance differences between my Mach II/ColdSpring/Transfer application and a standard Mach II/ColdSpring, particularly in my development environment. My shared hosting environment is not a good testing bed for anything performance-wise as my server is often unreliable, but generally the performance on this has been good as well. The only issues I have noticed have been on pages that don't even use any Transfer objects (the home page uses none) which is likely due to performance issues on the host than Transfer.
Want to learn more about Transfer?
Well, I have been talking to Mark Mandel who has agreed to give an online presentation on Transfer in a couple weeks as part of my continuing open-source Breezos, so look for an announcement soon regarding that. Also, Mark has a number of examples and presentations on his site. Lastly, you can sign up for the mailing list. I will also continue to post about it as I continue to use it.
Just to add another comment to your article - you said:
'If you need to customize your generated Transfer objects, the framework offers a means to inject functions into the objects via the config'
This is true, however, in 0.6 (very soon out!) there is another way to skin a cat - you can setup a decorator with each object definitions - i.e. within your config you would have <object decorator="mycfc.Author">...</object>
This allows you to write your own CFC which is created by Transfer and automagically wraps around the original generated TransferObject and extends all the public methods of the generated object.
This means you can write your own CFC code as you would normally, as well as leverage the already generated object.
Just thought I would throw that in there as well.
Keep up the good work!
I'll second Mark's comment about the decorator mechanism - it's a great way to wrap / extend the functional of the base Transfer objects. We use it to allow custom data formats (such as an empty string for a null date).
Also very useful are the ignore-insert / ignore-update and refresh-insert / refresh-update attributes on columns that help you work with fields that are automatically updated by triggers.
I also like the ability to specify packages and therefore map the flat structure of the database to a number of related objects within separate packages, e.g., trial.info and trial.notification.

