A Beginner's Guide to the ColdSpring Framework for ColdFusion
Join the DZone community and get the full member experience.
Join For FreeIf 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.
Published at DZone with permission of , DZone MVB. See the original article here.
Opinions expressed by DZone contributors are their own.
Comments