Understanding the Difference Between Coldspring and Transfer Objects
Recently I've been hard at work trying to understand the core principles of OO programming in ColdFusion. Last week Ben Nadel shared his experiences at a week-long course devoted to OOP with Hal Helms (the summary, which can be found here, also has links to his daily briefs). His posts opened my eyes to a couple of concepts that are now new to me, and since I'm on a new project I thought it'd be a great opportunity to try them out. As you know from my previous posts, I'm also trying to implement IoC (Coldspring) and ORM (Transfer-ORM) frameworks into my project, so figuring out where these two stand in relation to what I think I now know about OOP is my next challenge.
Singleton vs. Transient Objects
The first hurdle I had to cross was figuring out who would manage my objects. At first, I thought that all objects would be managed by Coldspring, both in the service (UserService) and domain (User) layers. The UserService object would have behaviors, validate the data and pass it off to the User object that would do some other stuff and then pass over control to Transfer for saving the object to the database. You can see from this that I was a little lost as to how my objects were meant to interact. After taking the time to read Coldspring's Quickstart guide, however, I stumbled upon this section: Managing Singleton vs. Transient Objects with ColdSpring.
In a nutshell, the guide explains the difference between singleton objects and transfer objects, and details the role Coldspring has managing these objects. Singleton objects are objects that are unique within the application, and are often instantiated on application startup. Service layer objects can be considered singletons because they are each unique, and don't have any instance data: they serve as an interface between your controller and your domain object. A transient object, on the other hand, can be represented several times in your application. There can be several different versions of a User, but there will always ever be only one UserService.
Coldspring's job is to manage singleton objects, not transients. Therefore, in our example, it will only serve the UserService object. Who, then, takes care of our User object? In a world where we have limited amounts of RAM and server crashes are always a possibility, we need ways to constantly persist our data. Luckily, we have databases for that. Our transient objects will need to be saved to the database anyways, so why not just use Transfer to manage them for us? Transfer creates all the setX and getX methods we might need, and takes care of persisting our instances. All we need now is to get them to talk to each other.
Setting Up Coldspring and Transfer for our UserService and User Objects
The first thing we'll do is tell Transfer how our users table is setup.
sb.Users definition in the Transfer configuration file
<object name='Users' table='sb_users'>
<id name='id' type='string' />
<property name='username' type='string' />
<property name='password' type='string' />
<property name='email' type='string' />
<property name='roleid' type='string' />
</object>
</package>
Now that Transfer knows about our users table, we can go ahead and configure Coldspring.
Coldspring configuration file
<beans default-autowire='byName'>
<!-- coldbox Factory -->
<bean id='ColdboxFactory' class='frameworks.coldbox_2_6_1.system.extras.ColdboxFactory' singleton='true'/>
<!-- Datasource bean for Transfer -->
<bean id='softbreeze' factory-bean='ColdBoxFactory' factory-method='getDatasource'>
<constructor-arg name='alias'>
<value>${TransferDSNAlias}</value>
</constructor-arg>
</bean>
<!-- Coldbox-transfer Config Factory -->
<bean id='TransferConfigFactory' class='frameworks.coldbox_2_6_1.system.extras.transfer.TransferConfigFactory' singleton='true' />
<!-- Transfer configuration -->
<bean id='TransferFactory' class='frameworks.transfer_1_1.TransferFactory' singleton='true'>
<constructor-arg name='configuration'>
<bean factory-bean='TransferConfigFactory' factory-method='getTransferConfig'>
<!-- Config Path -->
<constructor-arg name='configPath'><value>${TransferConfigPath}</value></constructor-arg>
<!-- Definitions Path -->
<constructor-arg name='definitionPath'><value>${TransferDefinitionsPath}</value></constructor-arg>
<!-- ColdBox Datasource Bean -->
<constructor-arg name='dsnBean'><ref bean='softbreeze' /></constructor-arg>
</bean>
</constructor-arg>
</bean>
<bean id='Transfer' factory-bean='TransferFactory' factory-method='getTransfer' />
<!-- softbreeze beans -->
<bean id='userService' class='softbreeze.model.userService' />
</beans>
It may look like alot, but most of this is simply getting Coldspring to setup a Transfer bean for use in Coldbox. The last bean, userService, references our actual cfc file. A really neat feature of Coldspring is it's autowire functionality. What this does is look in each object and search for setX methods. For example, consider a setTransfer method in our userService object.
The userService's setTransfer method
<cfargument name='transfer' type='any' required='true' />
<cfset variables.transfer = arguments.transfer />
</cffunction>
When Coldspring sees this method, it knows to pass in the Transfer bean to it. This way, dependant beans are automagically inserted where needed (you could also do this using Coldspring's constructor-args, but for simple situations why not use simple solutions?). Now, how do we get all of this to work together? Let's imagine that our user is registering on our site. The event requested turns around and asks the userService object to register the new user. The userService object then validates the information provided and if everything is OK it then updates the user instance through Transfer. Let's go through that again step by step.
The event handler asks the userService objet to register the user
<cfargument name='event' type='any' required='true' />
<cfset var locals = { rc = event.getCollection() } />
<!---
The ioc variable in our request collection is Coldbox's Coldspring plugin.
--->
<cfset locals.userService = locals.rc.ioc.getBean( 'userService' ) />
<cfset locals.rc.saveResult = locals.userService.register( arguments.event ) />
<!--- Show the view or run another event. --->
</cffunction>
The controller doesn't really have a lot to do. It gets the userService bean (object) from Coldspring and then asks it to register the new user.
The userService object's register method
<cfargument name='event' type='any' required='true' />
<!---
Prepare some local variables.
--->
<cfset var locals = {
result = {
success = false, errors = { }
},
mandatory_fields = [ 'username', 'password', 'password_confirm', 'email' ]
} />
<!---
Loop over the mandatory_fields variable, and validate that they are not empty.
--->
<cfloop array='#locals.mandatory_fields#' index='locals.field'>
<cfif not len( event.getValue( locals.field, '' ) )>
<cfset locals.result.errors[ locals.field ] = 'MANDATORY_FIELD' />
</cfif>
</cfloop>
<!---
If the password_confirm field isn't empty, it must be the same as the
password field.
--->
<cfif len( event.getValue( 'password_confirm', '' ) ) and event.getValue( 'password', '' ) neq event.getValue( 'password_confirm', '' )>
<cfset locals.result.errors[ 'password_confirm' ] = 'DOESN_T_MATCH_PASSWORD' />
</cfif>
<!---
If there are no errors save the user and report a success.
--->
<cfif structIsEmpty( locals.result.errors )>
<!---
Create a new user instance from transfer.
--->
<cfset locals.newUser = variables.transfer.new( 'sb.Users' ) />
<cfset locals.newUser.setID( createUUID() ) />
<cfset locals.newUser.setUsername( event.getValue( 'username' ) ) />
<cfset locals.newUser.setEmail( event.getValue( 'email' ) ) />
<cfset locals.newUser.setPassword( hash( event.getValue( 'password' ), 'SHA-256' ) ) />
<cfset variables.transfer.save( locals.newUser ) />
<cfset locals.result.success = true />
</cfif>
<cfreturn locals.result />
</cffunction>
There seems to be a lot going on here, but there really isn't that much. I start by looping over my mandatory_fields array to check for missing input data. Then, I make sure the password_confirm field matches with the password field (if it isn't empty). If all is good (no errors), we ask Transfer for a new user, and we set it's values using the automatically created setX methods. When all updates are done, we can turn around again and ask Transfer to save our newly created user. When the method is done, we return our results variable. With it, the controller can decide whether to show a message confirming registration, or display the form again with the errors provided.
And there you have it. This obviously doesn't represent a complete registration form, but it does show how to integrate Coldspring and Transfer together with Coldbox. I'm still not 100% sure this is best practice yet, but I am starting to feel more and more comfortable with this way of doing things. There is little to no link between the controller and the domain object, and all the interaction is handled through the service layer.
Any thoughts?


2.) Why don't you use decorator to add Register behavior to User?
Thanks for the comments. I'm pretty new to all of this, so any insight is welcome :).
1) I understand the reasoning behind this. However, where does i18n happen? In the controller? I feel like this and parsing values before passing them to the service layer will only bloat my controller. Any ideas on this?
2) That's a really good idea. Like I said, I'm pretty new to all this so I hadn't considered that option yet. In this case, then, is there even a need for a service layer? How will I enforce stricter validation (the user name doesn't already exist, etc.). Can the domain object (through Transfer and the decorators) know about all of this?
Again, thanks for your insight on this ;).
Have a set of CFC "Managers" that take your service upon init(). Build your managers to provide functionality to your application and which take 'event' as a parameter. This keeps your interface consistent from your app framework and coldspring, allows you to clean up any "dirty" data within the Manager that would otherwise bloat your controller, and keeps the service layer framework independent.
So if I understand you correctly, my controller should send the event to the manager, which would clean it up and ping the service layer. The service layer would then ask the domain object for it's data/properties?
controller > manager > service object > domain object?
By domain object, I mean using Transfer to retrieve, set and save data.