Objects and Composition in CFCs - Part 2: ColdSpring
Note: as a reminder, I will be covering these same topics in my upcoming CFFrameworks.com workshop. So sign up.Installing ColdSpring
Obviously, first you need to go download the ColdSpring Framework created by Dave Ross and Chris Scott. Unpackage the zip file and place the "coldspring" directory in the root of your site. Depending on your local development set up you may require a mapping to ensure that the framework can be found at "/coldspring", however in a typical multi-site setup just putting it under the root will work (and, yes, it works in shared hosting environments).
Configuring ColdSpring
ColdSpring is configured using XML (see the docs for the full XML specs). This is where my code generator can help. The default template generates a ColdSpring XML snippet with each set of components created.
To start, I generated the base XML against the "console" table and pasted that into a /config/services.xml file. You will see below that this already handles passing the consoleDAO and consleGateway to my service component as well as passing the DSN to both the gateway and DAO - more on that later.
<?xml version="1.0" encoding="UTF-8"?>
<beans>
<bean id="consoleDAO" class="com.xbox.consoleDAO">
<constructor-arg name="dsn">
<value>${dsn}</value>
</constructor-arg>
</bean>
<bean id="consoleGateway" class="com.xbox.consoleGateway">
<constructor-arg name="dsn">
<value>${dsn}</value>
</constructor-arg>
</bean>
<bean id="consoleService" class="com.xbox.consoleService">
<constructor-arg name="consoleDAO">
<ref bean="consoleDAO"/>
</constructor-arg>
<constructor-arg name="consoleGateway">
<ref bean="consoleGateway"/>
</constructor-arg>
</bean>
</beans>
Now, I would also like to pass my DAOs and Gateways for each of other objects (i.e. the games, controls and accessories) into the consoleService. So, I generate the XML for each of these items, copy paste the DAO and Gateway bean definitions into the XML and the contructor-args for the service and paste tham into my consoleService definition. When you are done, the XML should look like follows:
<?xml version="1.0" encoding="UTF-8"?>
<beans>
<!-- console -->
<bean id="consoleDAO" class="com.xbox.consoleDAO">
<constructor-arg name="dsn">
<value>${dsn}</value>
</constructor-arg>
</bean>
<bean id="consoleGateway" class="com.xbox.consoleGateway">
<constructor-arg name="dsn">
<value>${dsn}</value>
</constructor-arg>
</bean>
<bean id="consoleService" class="com.xbox.consoleService">
<constructor-arg name="consoleDAO">
<ref bean="consoleDAO"/>
</constructor-arg>
<constructor-arg name="consoleGateway">
<ref bean="consoleGateway"/>
</constructor-arg>
<constructor-arg name="controlDAO">
<ref bean="controlDAO"/>
</constructor-arg>
<constructor-arg name="controlGateway">
<ref bean="controlGateway"/>
</constructor-arg>
<constructor-arg name="accessoryDAO">
<ref bean="accessoryDAO"/>
</constructor-arg>
<constructor-arg name="accessoryGateway">
<ref bean="accessoryGateway"/>
</constructor-arg>
<constructor-arg name="gameDAO">
<ref bean="gameDAO"/>
</constructor-arg>
<constructor-arg name="gameGateway">
<ref bean="gameGateway"/>
</constructor-arg>
</bean>
<!-- accessory -->
<bean id="accessoryDAO" class="com.xbox.accessories.accessoryDAO">
<constructor-arg name="dsn">
<value>${dsn}</value>
</constructor-arg>
</bean>
<bean id="accessoryGateway" class="com.xbox.accessories.accessoryGateway">
<constructor-arg name="dsn">
<value>${dsn}</value>
</constructor-arg>
</bean>
<!-- control -->
<bean id="controlDAO" class="com.xbox.controls.controlDAO">
<constructor-arg name="dsn">
<value>${dsn}</value>
</constructor-arg>
</bean>
<bean id="controlGateway" class="com.xbox.controls.controlGateway">
<constructor-arg name="dsn">
<value>${dsn}</value>
</constructor-arg>
</bean>
<!-- game -->
<bean id="gameDAO" class="com.xbox.games.gameDAO">
<constructor-arg name="dsn">
<value>${dsn}</value>
</constructor-arg>
</bean>
<bean id="gameGateway" class="com.xbox.games.gameGateway">
<constructor-arg name="dsn">
<value>${dsn}</value>
</constructor-arg>
</bean>
</beans>
Initializing ColdSpring
I think before we get into the ColdSpring code required, it is important to note a few things. First, the power of ColdSpring doesn't easily come through on a small application with only a few dependencies involved, as in this case. It really shines as your application grows and your dependencies become more complex. Second, this could have been done using autowiring which would have required much less XML with only some slight modification to how we pass the dependencies into the service (i.e. using setter arguments instead of constructor arguments - check the docs for more information on this). Lastly, let's look at the code required to create my service previously:
<cfset dsn = "xbox" />
<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 let's look at the new code:
<cfset props = structNew() />
<cfset props.dsn = "xbox" />
<cfset factory = createObject("component","coldspring.beans.DefaultXmlBeanFactory").init(defaultProperties=props)/>
<cfset factory.loadBeansFromXmlFile(expandPath("/config/services.xml"),true)/>
<cfset consoleService = factory.getBean("consoleService") />
To me, this looks so much easier to maintain. Let's examine what is going on here. First, remember the "$dsn" values in my XML above, well, ColdSpring allows us to define a default properties structure where it will pull values referenced in that manner. Thus, I am passing a simple structure with my DSN only in this case. Second, after creating the the ColdSping factory object, I pass it my XML config file path. Finally, I ask for the bean from the factory which is passed back to me with all the dependencies already in place. From this point on, my sample application looks and works exactly as before.
Conclusion
Hopefully this has served as a good example of getting started using ColdSpring, but I need to reinforce that I have only touched a portion of what ColdSpring can do once you delve into it - for instance, I have not shown autowiring, aspect-oriented programming (AOP), remote proxies and automatic CFC to ActionScript object conversion. Nonetheless, simply using the portion that I have shown, I think you can see how ColdSpring can make your applications more flexible and easier to maintain by simplifying and centralizing managing dependencies.
Both of the posts in this series got me thinking about object composition, so I modelled both your console and consoleService as domain and application models (respectively) in UML. You can check it out at: http://www.fancybread.com/blog/index.cfm/2007/6/5/Application-and-Domain-Models-Examples-of-Aggregate-and-Composite-Design
I finally had time to sit and read this post as a follow up on your last post on composition. Very helpful and useful as always. Up next -- Transfer!
By the way, for a good intro to Coldspring's autowiring capabilities, I found this very good blog post by Nando: http://aria-media.com/blog/index.cfm/2006/9/22/The-Mystery-of-AutoWiring
(even though it refers to the Model Glue framework, I didn't find the explanation to only be applicable to MG).
Question on Coldspring and your generated code.
I notice in the generated xxxxService.cfc that there is the line:
<cfset var XXXX = createObject("component","XXXX").init(argumentCollection=arguments) />
in the createXXXX() method
I take it that if using Coldspring (or any DI object factory) instead of creating an object directly within the CFC you would just use Coldspring to give it one via a setter method or as an argument in the init() method?
Alan Livie
ColdSpring is generally used for handling service layer dependencies for service singletons (which in CF is an object that exists only once in memory - usually in Application scope though in CF there is no "true" concept of a singleton).
HTH or did I make things more confusing?

