Using ColdSpring Factory Beans

The ability to use your own Factory CFCs from within ColdSpring is a very powerful feature. It makes it easy to create your own custom Factories, or to leverage other CFC frameworks such as Object-Relational Mapping (ORM) frameorks.

As already mentioned in the section on Singletons, ColdSpring isn't idea for managing Domain Objects like Users or Products. So how can we deal with this, since Domain Objects like those are quite possibly the most important part of our application?

ColdSpring provides support for Factory Beans, which are special kinds of objects that create and return other objects. This example is a bit contrived for the sake of simplicity in demonstrating how factory beans work, so bear with us. We'll look at a more real-world usage in a moment. For example, say we wanted to be able to create a new Product object that our ProductService can do something with. Let's look at the code:

ProductService.cfc

<cfcomponent name="ProductService" hint="">
	
	<cffunction name="init" access="public" returntype="any" hint="Constructor.">
		<cfreturn this />
	</cffunction>
	
	<cffunction name="getProduct" access="public" returntype="any" output="false" hint="I return a Product based on the passed ID.">
		<cfargument name="productID" type="numeric" required="true" />
		<cfreturn getProductFactory().createInstance(arguments.productID) />
	</cffunction>
	
	<cffunction name="getProductFactory" access="public" returntype="any" output="false" hint="I return the ProductFactory.">
		<cfreturn variables.instance['productFactory'] />
	</cffunction>
		
	<cffunction name="setProductFactory" access="public" returntype="void" output="false" hint="I set the ProductFactory.">
		<cfargument name="productFactory" type="any" required="true" hint="ProductFactory" />
		<cfset variables.instance['productFactory'] = arguments.productFactory />
	</cffunction>

</cfcomponent>

Product.cfc

<cfcomponent name="Product" hint="I model a Product.">
	
	<cffunction name="init" access="public" returntype="any" hint="Constructor.">
		<cfargument name="productID" type="numeric" required="true" />
		<cfset setProductID(arguments.productID) />
		<cfreturn this />
	</cffunction>
	
	<cffunction name="getProductID" access="public" returntype="numeric" output="false" hint="I return the ProductID.">
		<cfreturn variables.instance['productID'] />
	</cffunction>
		
	<cffunction name="setProductID" access="public" returntype="void" output="false" hint="I set the ProductID.">
		<cfargument name="productID" type="numeric" required="true" hint="ProductID" />
		<cfset variables.instance['productID'] = arguments.productID />
	</cffunction>

</cfcomponent>

GenericFactory.cfc

<cfcomponent name="GenericFactory" hint="I create and return Factories for Domain Objects.">
	
	<cffunction name="init" access="public" returntype="any" hint="Constructor.">
		<cfreturn this />
	</cffunction>
	
	<cffunction name="createFactory" access="public" returntype="any" output="false" hint="I get an instance the specified kind of Factory.">
		<cfargument name="factoryType" type="string" required="true" />
		<cfreturn CreateObject('component', '#arguments.factoryType#Factory').init() />
	</cffunction>

</cfcomponent>

ProductFactory.cfc

<cfcomponent name="ProductFactory" hint="I build Product domain objects.">
	
	<cffunction name="init" access="public" returntype="any" hint="Constructor.">
		<cfreturn this />
	</cffunction>
	
	<cffunction name="createInstance" access="public" returntype="any" output="false" hint="I get an instance of a Product object.">
		<cfargument name="productID" type="numeric" required="true" />
		
		<!--- 
		In a real application, this might query the database to get the product data for
		that ID and use it to populate the Product. You might call a Gateway object to
		actually run the query, and of course the Gateway could be injected into the
		Factory with ColdSpring!
		--->
		<cfreturn CreateObject('component', 'Product').init(arguments.productID) />
	</cffunction>

</cfcomponent>

In a nutshell, what we want to do here is:

  • Ask the service for a Product based on a specified Product ID.
  • The service will ask the ProductFactory for the right Product.
  • The ProductFactory is supplied to the ProductService by using a GenericFactory that can create factories for different kinds of domain objects (so it could give us a ProductFactory, an AddressFactory, etc.)
The idea being that the GenericFactory knows how to create lots of different kinds of Factories for us. That's all that is important for now. Lets have a look at the ColdSpring XML configuration that would allow us to do this:

<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd">

<beans default-autowire="byName">
	
	<bean id="productService" class="coldspring.examples.quickstart.components.ProductService" />
	
	<bean id="productFactory" factory-bean="genericFactory" factory-method="createFactory">
		<constructor-arg name="factoryType">
			<value>Product</value>
		</constructor-arg>
	</bean>
	
	<bean id="genericFactory" class="coldspring.examples.quickstart.components.GenericFactory" />
		
</beans>

Note the special "factory-bean" and "factory-method" attributes on the XML for "productFactory". Also note that it doesn't have a "class" attribute, where we would normally specify what CFC that bean is. What this means is that when ColdSpring creates the ProductFactory, it actually calls whatever bean is specified in the "factory-bean" and invokes whatever method is specified in the "factory-method". It also passes in any arguments that we specify. So in this case, when ColdSpring creates the ProductFactory, it does so by calling "genericFactory.createFactory('Product')". Whatever that gives back is what becomes the "productFactory" bean.

As promised, we'll show a more "real world" example in a minute. But just to prove that this works, we can run the following code:

<cfset productService = beanFactory.getBean('productService') />
<cfset product = productService.getProduct(14) />
Should return 14 since that is the Product ID we used to create the Product: #product.getProductID()#

When we run that, we get:
Should return 14 since that is the Product ID we used to create the Product: 14

A more "real world" version of this can be shown by adding in the Transfer ORM framework. If you have no idea what Transfer is, don't worry. The real point is just to show how we can use ColdSpring's factory method capabilities to make use of external factories. Transfer just happens to be a very common use case.

As we've said, Transfer is an ORM, which means it can help create Domain Objects. I might want to use it in a UserGateway to create User objects (and their related objects) for me. Here is how we could make use of Transfer as part of our ColdSpring configuration:

<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd">

<beans>
	
	<bean id="transferFactory" class="transfer.transferFactory">
	   <constructor-arg name="datasourcePath"><value>datasource.xml</value></constructor-arg>
	   <constructor-arg name="configPath"><value>transfer.xml</value></constructor-arg>
	   <constructor-arg name="definitionPath"><value>/definitions</value></constructor-arg>
	</bean>
	
	<bean id="transfer" factory-bean="transferFactory" factory-method="getTransfer" />
	
	<bean id="userGateway" class="coldspring.examples.quickstart.components.UserGateway">
		<property name="transfer">
			<ref bean="transfer" />
		</property>
	</bean>
	
</beans>

So here is a classic use for ColdSpring factory beans. When ColdSpring creates the UserGateway, it sees that it has a dependency on the Transfer bean. In order to create the Transfer bean, ColdSpring calls "getTransfer()" on the TransferFactory bean. It gives back an instance of the Transfer CFC, and that is what gets injected into the UserGateway. The UserGateway now has a handle on Transfer, and can use it however it needs to.

You'll find that people use ColdSpring factory beans in a number of places where they need to make use of other code libraries or custom factories to help create things that ColdSpring isn't meant for. This might take a bit to sink in, so just have a look at the code and check around on the web for many more examples of using factory beans.