Over a million developers have joined DZone.

A Beginner's Guide to the ColdSpring Framework for ColdFusion

·

If you ask a ColdFusion developer what they think of the ColdSpring framework, chances are that you will get one of two answers. Either they will gush about how useful it is and how they couldn't imagine building large-scale applications without it or they will say they don't really understand the point. In my experience this is because until you encounter the problems that ColdSpring solves, it can seem like a complex solution to a problem you never had. However, when you begin building more complex applications, the benefits of a framework like ColdSpring quickly become apparent.

This guide aims to introduce the problems that ColdSpring can help to solve and provide enough information to get started using it. We'll begin by looking at how these problems are generally managed without ColdSpring and then compare that to a sample with ColdSpring to hopefully illustrate some of the benefits. While I won't go into great depth and our example applications are simple, we'll cover a number of areas including configuring ColdSpring for managing dependencies, aspect oriented programming and generating remote proxies.

Note: this article and samples are based upon a presentation I have developed, so if you have a user group that you think would benefit from this information, feel free to contact me about presenting.

What Problems Does ColdSpring Solve?
Where most people get the most mileage out of ColdSpring is specifically its ability to centralize and simplify management of complex object dependencies. For example, if you have an application that has an authentication service component (CFC) that is dependent on a user service, ColdSpring can help you manage that.

You may wonder why you need a framework to manage something so simple as passing an dependency in via a constructor/method or initializing it in the constructor. The thing is that this example, where service A is dependent on service B is far too simplistic to cover the benefits of using the framework. In a real world application, service A is dependent on services B through L and vice-versa. It is when you start dealing with this web of dependencies in a typical large application that using ColdSpring really pays off. In fact, I think its safe to say that the larger your application, the bigger the payoff.

Managing Dependencies Without ColdSpring
First, let's look at the problem by examining how you might manage dependencies without ColdSpring. Our example "application" is has uses the sample "cfartgallery" datasource that is installed by default with ColdFusion. Our code has two objects, "art" and "artist" whereby an art has an artist. Furthermore, we have created a DAO and Gateway CFC for the art and artist objects as well as a service which is our API for accessing both art and artists. The code for all these examples is available as a zip or via SVN at my Google Code repository.

The dependencies that exist within this simplistic example are as follows:

  • ArtDAO requires ArtistDAO;
  • ArtService requires ArtDAO, ArtGateway, ArtistDAO and ArtistGateway;
  • ArtService, ArtDAO, ArtGateway, ArtistDAO and ArtistGateway all require a string for the DSN.

Keep in mind that this example is simple and we already have a good number of dependencies to handle. If you extrapolate that out for a typical application you can see how complex this issue can get.

I have developed two variations of handling these sorts of dependencies without ColdSpring. The first is where we put all the createObject code in the constructor (i.e. init) and the second has the dependencies passed in. Let's look at the init() method of the service in the first option:

 

<cffunction name="init" access="public" output="false" returntype="com.nocs.artGalleryServiceOption1">
<cfargument name="dsn" type="String" required="true" />

<cfset variables.artDAO = createObject("component","com.nocs.art.artDAO").init(arguments.dsn) />
<cfset variables.artGateway = createObject("component","com.nocs.art.artGateway").init(arguments.dsn) />
<cfset variables.artistDAO = createObject("component","com.nocs.artists.artistDAO").init(arguments.dsn) />
<cfset variables.artistGateway = createObject("component","com.nocs.artists.artistGateway").init(arguments.dsn) />

<cfreturn this />
</cffunction>

 

As you can see, while we pass in the datasource string to the service and then to each of the objects, we then create each of the dependencies independently. The same goes for the init() of artDAO which, as we mentioned, requires the artistDAO:

 

<cffunction name="init" access="public" output="false" returntype="com.nocs.art.artDAO">
<cfargument name="dsn" type="string" required="true">

<cfset variables.dsn = arguments.dsn />
<cfset variables.artistDAO = createObject("component","com.nocs.artists.artistDAO").init(arguments.dsn) />

<cfreturn this>
</cffunction>

 

This, in and of itself, poses one of the issues with this approach, which is that we duplicate creating the artistDAO when we could reuse that. Additionally, if we were to make a change to where artistDAO is stored or what arguments it requires, we already need to replicate those changes to all copies of the createObject code. In a large application, this can become unmanageable.

Perhaps, you say, we can mitigate some of these problems by passing in the dependencies instead. Personally, if I am forced to build an application without ColdSpring, this is my preferred solution. In this case, the service's constructor looks like this:


<cffunction name="init" access="public" output="false" returntype="com.nocs.artGalleryServiceOption2">
<cfargument name="artDAO" type="com.nocs.art.artDAO" required="true" />
<cfargument name="artGateway" type="com.nocs.art.artGateway" required="true" />
<cfargument name="artistDAO" type="com.nocs.artists.artistDAO" required="true" />
<cfargument name="artistGateway" type="com.nocs.artists.artistGateway" required="true" />

<cfset variables.artDAO = arguments.artDAO />
<cfset variables.artGateway = artGateway />
<cfset variables.artistDAO = artistDAO />
<cfset variables.artistGateway = artistGateway />

<cfreturn this />
</cffunction>

 

However, the logic for creating those objects still needs to exist, so, in the case of the example code, we have it in nocsoption2.cfm:


<cfset dsn = "cfartgallery" />
<cfset artistDAO = createObject("component","com.nocs.artists.artistDAO").init(dsn) />
<cfset artDAO = createObject("component","com.nocs.art.artDAO").init(dsn,artistDAO) />
<cfset artGateway = createObject("component","com.nocs.art.artGateway").init(dsn) />
<cfset artistGateway = createObject("component","com.nocs.artists.artistGateway").init(dsn) />
<cfset artGalleryService = createObject("component","com.nocs.artGalleryServiceOption2").init(artDAO,artGateway,artistDAO,artistGateway) />

 

While this, in my opinion, is preferable to the creatObjects in the constructor, you'll notice that it can get messy and complex as the application grows. Another issue is that the order is important. For example, since artDAO needs artistDAO, we create artistDAO first. This is simple to resolve, but once you have a large number of components each with their own dependencies it can become complicated to ensure that everything is created in the proper order to accommodate those dependencies.

Simple Dependency Injection with ColdSpring
Now let's take a look at how you can manage these same dependencies using ColdSpring. Of course, your first step is to download the latest version of ColdSpring from the ColdSpringFramework.org site (note that as of this writing, the version is 1.2) and add it to your site.

Next, let's take a look at how to configure ColdSpring. ColdSpring is configured using an XML file. In the example below I have explicitly defined the dependencies of each object ColdSpring manages as constructor arguments (i.e. passed to the init() method) - as you can see, for example, in the artDAO definition which includes a reference to artistDAO. One important thing to note is that the order in which the objects are defined is no longer a concern.

 

<?xml version="1.0" encoding="UTF-8"?>

<beans>
<bean id="artDAO" class="com.withcs.art.artDAO">
<constructor-arg name="dsn"><value>${dsn}</value></constructor-arg>
<constructor-arg name="artistDAO">
<ref bean="artistDAO"/>
</constructor-arg>
</bean>
<bean id="artGateway" class="com.withcs.art.artGateway">
<constructor-arg name="dsn"><value>${dsn}</value></constructor-arg>
</bean>
<bean id="artGalleryService" class="com.withcs.artGalleryService">
<constructor-arg name="artDAO">
<ref bean="artDAO"/>
</constructor-arg>
<constructor-arg name="artGateway">
<ref bean="artGateway"/>
</constructor-arg>
<constructor-arg name="artistDAO">
<ref bean="artistDAO"/>
</constructor-arg>
<constructor-arg name="artistGateway">
<ref bean="artistGateway"/>
</constructor-arg>
</bean>
<bean id="artistDAO" class="com.withcs.artists.artistDAO">
<constructor-arg name="dsn"><value>${dsn}</value></constructor-arg>
</bean>
<bean id="artistGateway" class="com.withcs.artists.artistGateway">
<constructor-arg name="dsn"><value>${dsn}</value></constructor-arg>
</bean>
</beans>

 

I should note at this point that if you think its too much work to write this (and your DAO's and other components) by hand, my Illudium PU-36 Code Generator will handle much of this for you (and it has a handy ColdFusion Builder extension now as well). Another item you may notice is the use of a constructor argument for DSN that passes a value "${dsn}". This is a handy feature in ColdSpring whereby you can create a structure containing property keys that can be referenced in your configuration file like that for handling constants, such as your DSN. So "${dsn}" is mapped to the "dsn" key on my properties structure defined below.


<cfset properties = StructNew() />
<cfset properties.dsn = "cfartgallery" />
<cfset beanFactory = CreateObject('component', 'coldspring.beans.DefaultXmlBeanFactory').init(defaultProperties=properties) />
<cfset beanFactory.loadBeans('/config/coldspring-option1.xml') />

 

The code above is the code used to instantiate the ColdSpring "beanfactory" where you will get any of the components managed by ColdSpring. For example, the following line loads the artGalleryService defined in the above configuration with all its dependencies already injected:


<cfset artGalleryService = beanFactory.getBean("artGalleryService") />

 

Now, I already mentioned that, if you thought the configuration file was too verbose or too much work, you could have Illudium do much of the work for you. However, ColdSpring itself has an "autowiring" feature that eliminates the need to explicitly define dependencies in your configuration file, eliminating much of the XML configuration needed. You simply need to have a setter method in your ColdSpring managed service for each dependency. For instance, the method below, when added to our ArtDAO will automatically have the ArtistDAO dependency injected into it without it being defined in the configuration file (keep in mind that the naming of the method is important).


<cffunction name="setArtistDAO" access="public" output="false" returntype="void">
<cfargument name="artistDAO" type="com.withcsoption2.artists.artistDAO" required="true" />

<cfset variables.artistDAO = arguments.artistDAO />
</cffunction>

 

To see how much leaner our XML configuration becomes, here is the same configuration file as above, but using autowiring.

 

<?xml version="1.0" encoding="UTF-8"?>

<beans default-autowire="byName">
<bean id="artDAO" class="com.withcsoption2.art.artDAO">
<constructor-arg name="dsn"><value>${dsn}</value></constructor-arg>
</bean>
<bean id="artGateway" class="com.withcsoption2.art.artGateway">
<constructor-arg name="dsn"><value>${dsn}</value></constructor-arg>
</bean>
<bean id="artGalleryService" class="com.withcsoption2.artGalleryService">
</bean>
<bean id="artistDAO" class="com.withcsoption2.artists.artistDAO">
<constructor-arg name="dsn"><value>${dsn}</value></constructor-arg>
</bean>
<bean id="artistGateway" class="com.withcsoption2.artists.artistGateway">
<constructor-arg name="dsn"><value>${dsn}</value></constructor-arg>
</bean>
</beans>

 

Our configuration went from 33 lines (for this very simple example) to only 18 but all the dependencies will still be injected just the same. Now, that's easy!


Handling "Aspects" with ColdSpring
The concept of aspects are something that is difficult to explain since it is, to me, very loosely defined. Other times you will hear aspects referred to as "cross-cutting concerns" which is a little more descriptive. Basically, it is some process that occurs throughout many portions of your application. I told you it was difficult to define!

The typical example used for a common aspect is logging. This is because all portions of your application would require access to logging regardless of the types of objects they manage. For instance, if you want to log all data writes for your application you'd find yourself copying the logging code into every DAO within your application (probably you'd have a logging CFC but the code to reference and call that CFC would appear in every DAO).

The problem with this example, to me, is that it becomes hard for someone new to the concept to see where you would use this other than logging. So let me explain a usage I had on a recent project. This project had versioning logic information for every database record insert, update and delete. This presented not just the problem of repeating the versioning code in every DAO but also it referenced the users ID which was stored in the session and which I wanted to keep out of my DAOs. Thus, by putting this logic inside an "advice" for AOP I was not only able to remove the need to add this logic to every DAO but also kept references to this session variable out of my DAOs (this is considered a best practice).

Let's see how this works. First you create an advice component like the one below. Advice can be called before, after or around the method calls you specify - for example, your logging you would probably want to happen after but my versioning example needed to happen before. The example below is for a before advice. This is definitely not a useful example as its purpose is simply to increment whatever ID you passed by 1, but its intended to offer a simple example of how AOP functions and how to configure it.


<cfcomponent output="false" extends="coldspring.aop.MethodInterceptor">

<cffunction name="init" returntype="any" output="false" access="public">
<cfreturn this />
</cffunction>

<cffunction name="invokeMethod" returntype="any" access="public" output="false">
<cfargument name="methodInvocation" type="coldspring.aop.MethodInvocation" required="true" />
<cfset var local = structNew() />

<cfif structKeyExists(arguments.methodInvocation.getArguments(),"art")>
<cfset arguments.methodInvocation.getArguments()["art"].setArtID(arguments.methodInvocation.getArguments()["art"].getArtID()+1) />
</cfif>

<!--- Proceed with the method call to the underlying CFC. --->
<cfset local.result = arguments.methodInvocation.proceed() />

<!--- Return the result of the method call. --->
<cfreturn local.result />
</cffunction>

</cfcomponent>

 

As you can see, first our method checks to see whether an argument called "art" was passed and, if so, increments the ArtID by one. Now let's see how you would configure this advice. First we define the advice object and then we define the "advisor" which points at this advice object and also defines which methods it applies to via the mappedNames property. In this case, we are using the asterisk to indicate that we want it to apply to all methods of whatever object we apply the advice to.


<bean id="sampleAdvice" class="com.withcsoption2.sampleAdvice" />

<bean id="sampleAdvisor" class="coldspring.aop.support.NamedMethodPointcutAdvisor">
<property name="advice">
<ref bean="sampleAdvice" />
</property>
<property name="mappedNames">
<value>*</value>
</property>
</bean>

 

At this point, we have defined the advice/advisor but not actually applied it to any ColdSpring managed object. To do that, we first define the original bean as before (see first 2 lines below) but call it something else (in this case we appended "proxy"). We do this because when we ask for the bean "artGalleryService" we want ColdSpring to return the version with the AOP advice added in, which you can see defined below. We simply tell ColdSpring which bean to reference (i.e. artGalleryServiceProxy) and what advice to use (i.e. sampleAdvice).


<bean id="artGalleryServiceProxy" class="com.withcsoption2.artGalleryService">
</bean>
<bean id="artGalleryService" class="coldspring.aop.framework.ProxyFactoryBean">
<property name="target">
<ref bean="artGalleryServiceProxy" />
</property>
<property name="interceptorNames">
<list>
<value>sampleAdvice</value>
</list>
</property>
</bean>

 

Now when we call any method in artGalleryService that has an art argument, an artID of 1 will actually become an artID of 2 - like I said, not useful in this case. One important thing to notice is that my service methods have not changed at all nor are they even aware that my advice is manipulating the data. This means that I can apply this advice to any components where it is needed without changing them and I can centralize this logic and change it whenever necessary without a major rewrite.

Generating "Remote Proxies" with ColdSpring
The last feature I will discuss is one that I admittedly don't use often but can be handy in some situations; this is ColdSpring's ability to auto-generate remote proxies for A remote proxy is a component that is used to make certain methods within your services API available to Flash/Flex remoting or web services. This is done rather than directly adding access="remote" to the service method.

Using ColdSpring we can automatically make any or all methods within a service component it manages available as remote. To configure this feature, you define your remote bean and target the service you wish to make available as remote. As you might assume with the serviceName and relativePath properties below, ColdSpring actually does create a physical file for you though you cannot edit it as it may be overwritten. The remoteMethodNames property functions just like the mappedNames value in AOP whereby it determines which methods the remote proxy will apply to (in this case, I specified all). The beanFactoryName is the name of the application scoped variable containing the ColdSpring bean factory within your application.


<bean id="artGalleryServiceRemote" class="coldspring.aop.framework.RemoteFactoryBean" lazy-init="false">
<property name="target">
<ref bean="artGalleryService" />
</property>
<property name="serviceName">
<value>artGalleryServiceRemote</value>
</property>
<property name="relativePath">
<value>/com/withcsoption2/</value>
</property>
<property name="remoteMethodNames">
<value>*</value>
</property>
<property name="beanFactoryName">
<value>beanFactory</value>
</property>
</bean>

 

Its worth noting that this remote proxy still uses the AOP advice I defined elsewhere in the configuration for the artGalleryService - this being one of the benefits of using the auto-generated proxies. However, as I mentioned I don't use this feature much, in part because you cannot modify the generated file. In part this is because remote proxies are simply far too easy to build but, more importantly, in many cases my remoting proxy methods actually do some data massaging for passing data to Flex or JavaScript, which becomes difficult when you auto-generate.

Conclusion
There's obviously more to ColdSpring than I was able to cover here. But, as I am sure you can see, it is very powerful and can become a major time-saver, especially on larger projects. If you are looking for more, be sure to reference the online documentation for the project. Also, I highly recommend Brian Kotek's blog for ColdSpring related tutorials and discussions - specifically on many of the more advanced possibilities the framework offers.

Special thanks to Peter Bell for reviewing this article before publishing.

Topics:

Published at DZone with permission of Brian Rinaldi, DZone MVB. See the original article here.

Opinions expressed by DZone contributors are their own.

The best of DZone straight to your inbox.

SEE AN EXAMPLE
Please provide a valid email address.

Thanks for subscribing!

Awesome! Check your inbox to verify your email so you can start receiving the latest in tech news and resources.
Subscribe

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}