Nov 7
When learning how to apply object oriented principles to ColdFusion development, one will assuredly encounter the subject of design patterns. Singleton, Factory, Strategy and Bridge are just some of the patterns that describe solutions to common problems in programming. For this example, I'll demonstrate a variation on the Factory Pattern for transient objects. What is a factory? At it's simplest, a factory encapsulates the creation of objects to provide a consistent api for clients. What is a transient object? A typical OO ColdFusion application will incorporate a combination of Singletons (one instance per application - often instantiated at application startup) and Transients (a per request object that is not maintained in a persistent scope).For instance, when invoking a "save" method on a service, I return a new "result" object. Therefore, each of my services needs to create a new instance of a result object. Rather than place CreateObject("component","path.to.Result") in all services, I use a generic factory pre-configured with object aliases and class paths. I can then use:
<cfset result = TransientFactory.create("Result")>
But what if the object has setter dependencies or an init() method with constructor args? For any setter dependencies, I incorporate Brian Kotek's beanInjector within the TransientFactory to autowire all matching ColdSpring managed beans. For consturctor args, there are two ways to pass arguments to an init() method.
1. passing a struct of key/value pairs as a second argument to create().
<cfset initArgs = StructNew()>
<cfset initArgs.myprop = "myvalue">
<cfset result = TransientFactory.create("Result",initArgs)>
2. using the virtual method new{objectName}. Transientfactory.newResult(argumentCollection=initArgs).
<cfset result = TransientFactory.newResult(myprop="myvalue")>
The second method of object creation is made possible by onMissingMethod.
Additionally, you can specify the name of a method that the transient factory will invoke after initialization and setter dependency resolution.
Here's a simple example for setting up the TransientFactory in ColdSpring. If you use some of Brian Kotek's excellent ColdSpring Bean Utils, you notice a reference to his BeanInjector component. This TransientFactory example is similar to Brian's TDOBeanInjectorObserver component that autowires ColdSpring managed singletons into transfer objects.
<beans>
<bean id="beanInjector" class="model.BeanInjector" singleton="true" />
<bean id="TransientFactory" class="model.TransientFactory" singleton="true">
<constructor-arg name="classes">
<map>
<entry key="ServiceResult">
<value>model.util.ServiceResult</value>
</entry>
<entry key="Timer">
<value>model.util.Timer</value>
</entry>
</map>
</constructor-arg>
<constructor-arg name="afterCreateMethod">
<value>setup</value>
</constructor-arg>
<property name="beanInjector">
<ref bean="beanInjector" />
</property>
</bean>
</beans>
Here is the full TransientFactory component code.
<cfcomponent displayname="TransientFactory" output="false" hint="I create Transient objects.">
<!--- public --->
<cffunction name="init" access="public" output="false" returntype="any" hint="returns a configured transient factory">
<cfargument name="classes" type="struct" required="false" default="#StructNew()#">
<cfargument name="afterCreateMethod" type="string" required="false" default="">
<cfset variables.classes = arguments.classes>
<cfset variables.afterCreateMethod = arguments.afterCreateMethod>
<cfreturn this />
</cffunction>
<cffunction name="create" access="public" output="false" returntype="any" hint="returns a configured, autowired transient">
<cfargument name="transientName" type="string" required="true">
<cfargument name="initArgs" type="struct" required="false" default="#structNew()#">
<cfset var obj = createObject("component",getClassPath(arguments.transientName))>
<cfif StructKeyExists(obj,"init")>
<cfinvoke component="#obj#" method="init" argumentcollection="#initArgs#" />
</cfif>
<cfset getBeanInjector().autowire(targetComponent=obj, targetComponentTypeName=arguments.transientName) />
<cfif StructKeyExists(obj,variables.afterCreateMethod)>
<cfinvoke component="#obj#" method="#variables.afterCreateMethod#" />
</cfif>
<cfreturn obj>
</cffunction>
<cffunction name="getClasses" access="public" output="false" returntype="struct" hint="returns map of classes that can be created.">
<cfreturn variables.classes>
</cffunction>
<cffunction name="onMissingMethod" access="public" output="false" returntype="any" hint="provides virtual api [new{transientName}] for any registered transient.">
<cfargument name="MissingMethodName" type="string" required="true" />
<cfargument name="MissingMethodArguments" type="struct" required="true" />
<cfif (len(arguments.MissingMethodName) gt 3) and (Left(arguments.MissingMethodName,3) is "new")>
<cfreturn create(Right(arguments.MissingMethodName,Len(arguments.MissingMethodName)-3),arguments.MissingMethodArguments)>
</cfif>
</cffunction>
<!--- private --->
<cffunction name="getClassPath" access="private" output="false" returntype="string" hint="returns the transient class path.">
<cfargument name="transientName" type="string" required="true">
<cfreturn variables.classes[arguments.transientName]>
</cffunction>
<!--- dependencies --->
<cffunction name="setBeanInjector" access="public" returntype="void" output="false">
<cfargument name="BeanInjector" type="any" required="true">
<cfset variables.BeanInjector = arguments.BeanInjector >
</cffunction>
<cffunction name="getBeanInjector" access="public" returntype="any" output="false">
<cfreturn variables.BeanInjector />
</cffunction>
</cfcomponent>
A big thanks to Bob Silverberg for co-authoring this component. You can grab a copy attached as an enclosure.
Nov 9, 2008 at 5:22 PM Paul,
How does this compare to using ColdSpring to instantiate transients?
Jaime
Nov 10, 2008 at 2:52 AM Hi Jaime,
Brian Kotek wrote a great explanation about using ColdSpring for transients. I could paraphrase, but I'll just post this from the quickstart that came out with release 1.2.
http://www.coldspringframework.org/coldspring/examples/quickstart/index.cfm?page=singletons
Nov 10, 2008 at 9:51 PM Good explanation from Brian. I was just after your take on whether and what extent your factory component avoids those caveats WRT transients. For example, is it indeed faster than ColdSpring?
Nov 11, 2008 at 6:02 PM Great stuff Paul!!
This functionality is built in to the new coming ColdBox 2.6.2 in the beanFactory plugin. It will autowire all your dependencies for you, cache if needed and even do after DI Complete methods. I'll post more on it soon.
But it not only creates transients, but you can declare cache metadata on the objects themselves and create singletons or time expired objects.
Nov 24, 2008 at 3:06 PM What do we need a Transient Factory?
Why don't we let the Service layer handle object creation?
e.g.: ABCservice.createABC() that returns CreateObject("component", ABC")
Nov 24, 2008 at 3:07 PM edit: WHY do we need a Transient Factory?, not 'what'.
Nov 24, 2008 at 3:24 PM Hi Henry,
I chose to build a generic transient factory to encapsulate the object creation in a single place. It allows me to configure the component paths rather than editing all my services should that path change.
In addition, if that transient has a dependency, the TransientFactory with autowire it for me.
Lastly, if I need to run a post-initialization and autowiring setup method, I can define that as well.
All this allows me to keep my service methods lean.
Dec 11, 2008 at 10:48 AM Paul, I'm considering the use of your TransientFactory. I like what I see here. One thing that wasn't obvious to me until actually testing it is that the "classes" constructor-arg is a struct of the names and paths to the transient objects you may wish to create. At first I was under the impression you could use this to create objects dynamically (I guess by passing in the class path). And it took me a minute to realize your "ServiceReturn" and "Timer" entries were examples and not necessary to run the Factory itself. I now understand that you register any potential class paths in the ColdSpring xml. Works for me, I just thought this note may help others coming along and trying this out.
Dec 16, 2008 at 8:13 PM Hi Matt,
Thanks for pointing that out. You're right, the ColdSpring example above is provided to demonstrate how one initializes the Transient Factory with the class paths to your transient objects. One could use a MapFactoryBean as well...
Jan 15, 2009 at 3:14 AM hi,
i understood most of the code, unable to understand, from where bininjector has been injected and what's in bin injector, bin injector is something in Coldspring or its part of this code sample, if yes, where is the code for bin injector in this example.
Jan 17, 2009 at 6:31 PM Hi prashant,
You can grab the beaninjector and many other useful utilities at http://coldspringutils.riaforge.org as referenced in the post.
Apr 13, 2009 at 3:47 PM This is a great idea. Thanks. I'm using this.