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.
The ColdSpring Quickstart Guide, by Brian Kotek, is also a great resource:
http://coldspringframework.org/coldspring/examples/quickstart/
Why is it so wrong to access application scoped singletons within your CFCs? Whether an artService.cfc accesses variables.instance.artDAO.read() or application.artDAO.read() the 2 are still coupled aren't they? Everything is being accessed/passed by reference anyways, right?
Aside from an encapsulation purity standpoint, where and at what point does that practice break down?
Because if it doesn't -- or doesn't very often, wouldn't I be using a framework to uncomplicate a practice (injecting dependencies) that in and of itself is an un-necessary complication?
To be clear: I'm not asserting - I know I am missing the picture -- I just want to know why I need to do this in the first place?
Thanks for the help!
Drew
So far I only have three of these implemented as application-scoped singletons, so it's not too bad (String, Date, List). It's a nice way to keep certain pieces of code in one place and just call the functions from anywhere as needed. Plus, keeping the objects in the application scope avoids the problem of having to instantiate and object every time you need it, thus reducing performance and coupling more pages to the objects.
Anyway, I recently had to use some of these functions within a bean I created. I wanted to avoid a direct call to the application-scoped object within the bean, but I just couldn't see how. I found this post by Dan Wilson.
http://www.nodans.com/index.cfm/2009/5/3/Hard-Coding-Scopes-In-CFCs-Is-A-No-No
OK, so I shouldn't be using the application scope directly within a CFC. I get that. But now what? What is the right way to call a particular function within an object? I could use cfinvoke and call the method directly, but that doesn't do much for decoupling.
Is the expectation here that the bean instantiate every object it's ever going to need on its own? So within the init() constructor, a variables.foo would created an instantiated object?
This sounds like it could get just as messy. Let me guess, this is where ColdSpring comes in :-)
Thanks for the guide, Brian!
I spaced out my response in paragraphs and what not. It doesn't seem like the line breaks got converted though. (Hence, everything I wrote clumped up into one paragraph.)
@Drew - the issue is that in keeping your CFCs dumb to implementation details like that you improve reusability as well as avoid tying them into aspects of your architecture that may change. So, to give a dumb example, your CFC becomes not only dependent on dsn but the fact that dsn is in a variable named "dsn" in the application scope.
However, from a purely practical standpoint, in order to place all these dependencies in the application scope to implement things in this manner, you'd still have to go through the steps I walk through in the "without coldspring" section (i.e. instantiating each item, and potentially in the right order so as not to encounter errors due to missing dependencies when instantiated). The net result is, you've now tied your CFC into specific implementation details and you really haven't saved any time or complexity in my opinion.
@jose - yes, it can get messy and that's exactly why I show how its generally done without coldspring to illustrate that. That is usually the point when you realize why ColdSpring is so useful.
@sameer - if your CFCs are all just function libraries, then you probably won't benefit from ColdSpring. Its really designed for wiring services that access an object oriented model.
@Drew, @Jose, @Sameer, some extra info on the problem that ColdSpring solves is over here:
http://learn.objectorientedcoldfusion.org/wiki/Dependency_Injection
