Importance of Understanding Factories, Part 1: ColdSpring Misunderstandings

As ColdFusion developers progress in the use of object oriented programming concepts with CFCs, our applications become more flexible and able to support additional interfaces such as Ajax or Flex through web services. We also potentially benefit just from overall code maintainability, extensibility, and reusability by using best practices and design patterns common in OOP.

The benefits are many, but there are certainly pitfalls along the way. Perhaps we're ready to begin using service objects. And we'd like to create and pass in our transient objects with ColdSpring as a factory for all objects.

Let's cover the groundwork of our example, and then consider a pitfall. Here's our transient object:

Bean.cfc

CFM:
  1. <cfcomponent hint="Represents a bean." output="false">
  2.  
  3.   <cffunction name="init" access="public"
  4.               returntype="Bean" output="false">
  5.     <cfargument name="x" type="any" required="true" />
  6.     <cfargument name="y" type="any" required="true" />
  7.     <cfargument name="z" type="any" required="true" />
  8.     <cfset setX(arguments.x) />
  9.     <cfset setY(arguments.y) />
  10.     <cfset setZ(arguments.z) />
  11.     <cfreturn this />
  12.   </cffunction>
  13.  
  14.   <!------------------- Config -------------------------> 
  15.   <cffunction name="setConfig" access="public"
  16.               output="false" returntype="void"
  17.               hint="Method to pass in configs.">
  18.     <cfargument name="Settings" type="struct" />
  19.     <cfset variables.instance.config=arguments.settings/>
  20.   </cffunction>
  21.  
  22.   <!------------------ Setters ----------------------->
  23.   <cffunction name="setX">
  24.     <cfargument name="newval" />
  25.     <cfset variables.instance.data.x=arguments.newval />
  26.   </cffunction>
  27.   <cffunction name="setY">
  28.     <cfargument name="newval" />
  29.     <cfset variables.instance.data.y=arguments.newval />
  30.   </cffunction>
  31.   <cffunction name="setZ">
  32.     <cfargument name="newval" />
  33.     <cfset variables.instance.data.z=arguments.newval />
  34.   </cffunction>
  35.  
  36.   <!------------------ Getters ----------------------->
  37.   <cffunction name="getMemento" access="public"
  38.               returntype="struct" output="false">
  39.     <cfreturn variables.instance />
  40.   </cffunction>
  41.  
  42. </cfcomponent>

This is an overly-simplified example of a bean. It just has 3 properties (X, Y, Z) and corresponding setter methods. It also has a typical init() method, a setConfig() method to pass in configuration, and a getter method so we can quickly dump the state of the object. It'll be enough for our samples.

Perhaps we might write a ColdSpring configuration like this:

XML:
  1. <bean id="BeanService" class="com.BeanService">
  2.   <constructor-arg name="Bean">
  3.     <ref bean="Bean" />
  4.   </constructor-arg>
  5. </bean>
  6. <bean id="Bean" class="com.Bean" singleton="false">
  7.   <property name="Config">
  8.     <map>
  9.       <entry key="dsn"><value>MyDSN</value></entry>
  10.     </map>
  11.   </property>
  12.   <constructor-arg name="x">
  13.     <value>One</value>
  14.   </constructor-arg>
  15.   <constructor-arg name="y">
  16.     <value>Two</value>
  17.   </constructor-arg>
  18.   <constructor-arg name="z">
  19.     <value>Three</value>
  20.   </constructor-arg>
  21. </bean>

This seems like it should work, eh? We're instantiating a service object, instantiating and configuring a bean, and passing the bean into the service object. Here's a really simple example of the bean service object:

BeanService.cfc

CFM:
  1. <cfcomponent hint="Handles beans." output="false">
  2.   <cffunction name="init">
  3.     <cfargument name="Bean" />
  4.     <cfset variables.Bean=Arguments.Bean />
  5.     <cfreturn this />
  6.   </cffunction>
  7.   <cffunction name="getBean">
  8.     <cfreturn variables.Bean />
  9.   </cffunction>
  10.   <cffunction name="workWithBean">
  11.     <cfscript>
  12.       var myBean=variables.Bean;
  13.       myBean.setX("I changed X on the bean!");
  14.       myBean.setY("I changed Y on the bean!");
  15.       return myBean;
  16.     </cfscript>
  17.   </cffunction>
  18. </cfcomponent>

This BeanService object has a constructor that receives the bean and stores it in its variables scope. It then has a the getBean() method so we can see the bean, and a workWithBean() method that simulates doing some work with the bean.

And now, the pitfall: ColdSpring singletons and objects passed by reference.

This code technically runs, and appears fine: The Bean instance that we pass into the service is not a singleton, so each BeanService we instantiate (if we were to instantiate multiple) would have a separate Bean instance. And workWithBean() appears to use a local var copy of Bean.

Wrong.

First, the Singleton vs. Transient Objects With ColdSpring page explains that ColdSpring isn't ideal for generating transient components. Its overhead is more appropriate for less frequent creation of components that typically go in a shared scope, such as in the Application scope. Performance aside, though, the approach of injecting a transient bean into a singleton like BeanService does technically work.

So now the second point, how BeanService is working with Bean in our example workWithBean() method. The key misunderstanding is that CFCs like the Bean object are passed by reference when being assigned to variables. So when we do var myBean=variables.Bean, the local myBean variable is pointing to the same copy of Bean that variables.Bean points to. When it executes methods that change the bean's state, the one copy of Bean is being altered. Whoah, that wasn't an intended behavior. This could have very negative consequences, with Bean not being in the state you might expect it to be in.

So, how to fix it? Hmm. I don't want to use CreateObject() because that would become a dependency we'd have to manage, and we'd have to handle properly configuring the bean. This was the whole point of injecting the bean with ColdSpring in the first place.

Well, a quick solution could be to just use Duplicate() to make a copy of the injected bean before working on it! So, we could replace our workWithBean() method with this:

CFM:
  1. <cffunction name="workWithBean">
  2.   <cfscript>
  3.     var myBean=Duplicate(variables.Bean);
  4.     myBean.setX("I changed X on the bean!");
  5.     myBean.setY("I changed Y on the bean!");
  6.     return myBean;
  7.   </cfscript>
  8. </cffunction>

Now, variables.Bean will always be the pristine, original bean injected by ColdSpring, and the local myBean variable will perform its work with a duplicate of that bean. Our code will now be working the way we intended; disaster has been averted.

Well, this does work. But it is neither graceful nor efficient, and it is not recommended. Consider Sean Corfield's blog post, Duplicate() is Bad For Your (Object's) Health. In an informal test, he clocked Duplicate() as being drastically slower—we're talking 50 to 1—than using CreateObject().

Ugh. If we're back to using CreateObject(), what good is using ColdSpring for managing these component relationships, and what about dealing with configuration? Enter the world of factories. Yes, ColdSpring itself is a factory, but it is a factory oriented around creating and configuring our singletons, which can include other factories whose purpose is to properly create transient objects like Bean.

So instead of having ColdSpring inject Bean into BeanService, we can make a BeanFactory and inject that into BeanService, giving BeanService the ability to create Beans to work with any time it needs to. This may seem like a lot of effort, but we'll come to see that creating transient factories is easy and will clean up the mess of an architecture in this sample to something graceful and maintainable.

In this post, I described a few of the mistakes that can be made as we start to utilize ColdSpring. Hopefully these mistakes point to the importance of factories—not just ColdSpring, but our own transient factories as well. In the next blog post, we'll rework the sample to use a factory and review the benefits the factory provides and how it doesn't duplicate the purpose of ColdSpring. We'll review one more scenario that underscores the importance of factories: the dreaded race condition. And I'll provide source code that has executable demonstrations of these undesirable mistakes in action. :-)

3 Responses to “Importance of Understanding Factories, Part 1: ColdSpring Misunderstandings”

  1. John Whish Says:

    Paul Marcotte has a Transient Factory as part of Metro (metro.riaforge.org) which works well with ColdSpring.

  2. Josh Says:

    Actually, his Transient Factory as described in his Nov 7 blog post is exactly where I was going with this. I knew someone would jump the gun. :-) One problem with these fantastic frameworks like ColdSpring, Transfer, etc. is that some people haven’t gone through the pain of discovering the problems that these frameworks handle so wonderfully.

    That’s why my blog entry is iterative in nature, walking through the fire, so to speak. My end goal is to walk through the simple nature of a factory, which I’m hoping will help reveal how useful they are, and to lead toward the use of something like Paul’s awesome transient factory.

  3. John Whish Says:

    Hey Josh – totally agree with you that you need to walk through the fire to understand what is going on under the hood (woah that was a mixed metaphor!)

    Sorry for jumping the gun, I salute your efforts and look forward to your future posts :)

Leave a Reply

  Theme Brought to you by Directory Journal and Elegant Directory.