Importance of Understanding Factories, Part 2: Transient Factories
In my last post, I discussed some of the mistakes that can be made when using ColdSpring as our factory. It became evident that using ColdSpring to inject transient objects—that is, objects that have a state for a momentary use and then are discarded—into our service objects or other related singletons is problematic. This is largely due to the concurrency issue with reusing that injected bean in the service object's methods, and mistakes that can be made if we forget about objects being passed by reference. Ray Camden also discussed this recently in ColdFusion and Pass by Reference versus Value.
The answer is to build a transient factory for those beans. We can do it without losing the benefits of ColdSpring's dependency and configuration management. And it doesn't take much to make a factory. Take a look at this sample that works with my previous post's code.
-
<cfcomponent>
-
-
<!----------------- Constructor ---------------------->
-
<cffunction name="init" returntype="BeanFactory">
-
<cfset variables.config=StructNew() />
-
<cfreturn this />
-
</cffunction>
-
-
<!------------------ Config -------------------------->
-
<cffunction name="setConfig" returntype="void">
-
<cfargument name="Settings" type="struct" />
-
<cfset variables.config=arguments.settings />
-
</cffunction>
-
<cffunction name="getConfig" returntype="struct">
-
<cfreturn variables.config />
-
</cffunction>
-
-
<!--------------- Create Methods --------------------->
-
<cffunction name="createBean" returntype="any">
-
<cfset var com=CreateObject("component","Bean") />
-
<cfset com.setConfig(getConfig()) />
-
<cfreturn com.init(argumentCollection=arguments) />
-
</cffunction>
-
-
</cfcomponent>
The factory has a setConfig() method, just like the Bean does, and can be used to conveniently inject all configuration information with ColdSpring. Now, let's look at the createBean() method where the action is. It performs 3 key actions: (a) Create a fresh object. (b) Pass in configuration. (c) Call the init() method and return the object.
Our ColdSpring file can now be rewritten like this:
-
<bean id="BeanService" class="com.BeanService">
-
<constructor-arg name="BeanFactory">
-
<ref bean="BeanFactory" />
-
</constructor-arg>
-
</bean>
-
<bean id="BeanFactory" class="com.BeanFactory">
-
<property name="Config">
-
<map>
-
<entry key="dsn"><value>MyDSN</value></entry>
-
</map>
-
</property>
-
</bean>
See, we use the same XML for injecting configuration, it's just injected it into BeanFactory. We then inject BeanFactory into BeanService, instead of the Bean itself. It can use the factory to get a fresh bean whenever it needs one—properly instantiated and configured.
An example of BeanService tweaked to accept and use the factory:
-
<cfcomponent hint="Handles beans." output="false">
-
<cffunction name="init">
-
<cfargument name="BeanFactory" type="BeanFactory" />
-
<cfset variables.BeanFactory=Arguments.BeanFactory />
-
<cfreturn this />
-
</cffunction>
-
<cffunction name="workWithBean">
-
<cfscript>
-
var myBean=variables.BeanFactory.createBean();
-
myBean.setX("I changed X on the bean!");
-
myBean.setY("I changed Y on the bean!");
-
return myBean;
-
</cfscript>
-
</cffunction>
-
</cfcomponent>
Let's make the factory better. First, your app probably has more than one type of transient object. You don't need a separate transient factory for each type of object. Just have a different createBeanName() method for each object type, with creation code specific for each object. And if your objects are all essentially created with the same conventions, you can handle create methods for all of them with a single onMissingMethod handler. Something like this:
-
<!--- Create Methods --->
-
<cffunction name="onMissingMethod" returntype="any">
-
<cfargument name="MissingMethodName" />
-
<cfargument name="MissingMethodArguments" />
-
<cfscript>
-
var method=Arguments.MissingMethodName ;
-
var args=Arguments.MissingMethodArguments ;
-
var classList="MyBean,DiffBean,AnotherBean";
-
var className=RemoveChars(method,1,6);
-
var com="";
-
if( Left(method,6)=="create" &&
-
ListFindNoCase(classList,className) )
-
{
-
com=CreateObject("component",className);
-
com.setConfig(getConfig());
-
if(StructKeyExists(com,"setFactory"))
-
com.setFactory(this);
-
return com.init(argumentCollection=args);
-
}
-
</cfscript>
-
</cffunction>
See how the first if statement checks to make sure the method is a valid method name: One that starts with "create" and ends with an acceptable class name as we define in our classList variable. So this onMissingMethod handler will work with createMyBean(), createDiffBean(), and createAnotherBean(). Note how it also checks for the existence of a setFactory() method in the object it creates. If it is there, it will use the method to inject itself into the new object! This is a simple example of how autowiring is accomplished.
Take it even further! Head over to Paul Marcotte's blog post, A Coldfusion Transient Factory Example, which shows how to make a very generic transient factory that receives configuration on which classes it should create through ColdSpring. Very cool stuff.
Review. Transient factories don't require a lot of code, and are a vital step to simplifying and centralizing our instantiation and configuration of transient objects, or objects that have a state. Doing so doesn't negate the need for ColdSpring, in fact it complements it as it was intended, since ColdSpring works well with instantiation of singletons, and transient factories as described here are used as singletons. By utilizing ColdSpring and a transient factory, we can be well on our way to cleaner and more organized development.
Get the files. Download sample code to play with: ColdSpring Bean Test. It walks us through the issues discussed in the previous post as well as using transient factories like we just discussed.
This post has gotten pretty long, so I'll save the race condition demonstration for "Part 3".
