Creating Gateways using a GatewayFactory for ORM
Joe Reinhart blogged earlier this week about his ColdFusion 9 ORM: Generic DAO wrapper. I've been playing around with something similar (although I don't have a dynamic finder method like Joe has) which uses a Gateway Factory and an Abstract Gateway and thought I'd post my code here in case anyone was interested.
First, let me explain what the problem is. I like to abstract out interaction with the database into seperate objects. I call them Gateways, but they are also called DAOs (see Sean Corfield's post The DAO and Gateway separation in CFML is nonsense). Each Entity that I want persisted (saved in the database) has it's own DAO/Gateway which handles the basic CRUD methods, so it makes sense to create a base class, which has these CRUD methods and then have your individual DAO/Gateways extend it.
Occasionally you need to write some custom HQL (SQL for hibernate) for your entity and so you can put that in your concrete DAO/Gateway, but a lot of the time, your only need the CRUD methods, so it gets a bit annoying to have to lots of cfcs which essentially looks like this:
<cfcomponent extends="AbstractGateway" hint="I am the User Entity DAO/Gateway" output="false">
<cffunction name="init" returntype="any" output="false">
<cfset super.init( entityname="User" )>
</cffunction>
<!--- all crud methods are inherited from AbstractGateway.cfc so nothing else needed --->
</cfcomponent>
And here is a simple AbstractGateway class
<cfcomponent hint="I am the Abstract DAO/Gateway" output="false">
<!---
========================================================================================
Purpose: Abstract Gateway
Created By: John Whish
----------------------------------------------------------------------------------------
I am a abstract class and should never be instantiated directly, only extended.
========================================================================================
--->
<cffunction name="init" access="public" returntype="any" hint="I am the constructor" output="false">
<cfargument name="entityname" required="true" hint="I am the name of the entity, for example 'User'">
<cfscript>
setEntityName( arguments.entityname );
return this;
</cfscript>
</cffunction>
<!---
=======================================================================================
Public
=======================================================================================
--->
<cffunction name="new" access="public" returntype="any" hint="I return a non-persisted object" output="false">
<cfscript>
return EntityNew( getEntityName() );
</cfscript>
</cffunction>
<cffunction name="get" access="public" returntype="any" hint="I return a single object" output="false">
<cfargument name="id" required="numeric" hint="I am the unique id of the entity">
<cfscript>
var result = "";
if ( Val( arguments.id ) == 0 )
{
result = new();
}
else
{
result = EntityLoadByPK( getEntityName(), arguments.id );
}
return result;
</cfscript>
</cffunction>
<cffunction name="save" access="public" returntype="void" hint="I update or save the entity" output="false">
<cfargument name="theEntity" required="true" hint="I am the entity">
<cfscript>
EntitySave( arguments.theEntity );
</cfscript>
</cffunction>
<cffunction name="delete" access="public" returntype="void" hint="I delete the entity" output="false">
<cfargument name="theEntity" required="true" hint="I am the entity">
<cfscript>
EntityDelete( arguments.theEntity );
</cfscript>
</cffunction>
<cffunction name="fetch" access="public" returntype="array" hint="I return an array of objects matching a filter" output="false">
<cfargument name="filter" required="false" type="struct" default="#StructNew()#" hint="I am a map of properties to filter by">
<cfargument name="sortorder" required="false" default="" hint="I am the properties to sort by">
<cfargument name="options" required="false" default="#StructNew()#" hint="I am a struct of options">
<cfscript>
return EntityLoad( getEntityName(), arguments.filter, arguments.sortorder, arguments.options );
</cfscript>
</cffunction>
<cffunction name="reload" access="public" returntype="void" hint="I reload the data from the database. Useful if you populate an entity with bad data and don't want it saved in that state" output="false">
<cfargument name="theEntity" required="true" hint="I am the entity">
<cfscript>
EntityReload( arguments.theEntity );
</cfscript>
</cffunction>
<!---
=======================================================================================
Private
=======================================================================================
--->
<cffunction name="getEntityName" access="private" returntype="string" output="false">
<cfscript>
return variables.instance.entityname;
</cfscript>
</cffunction>
<cffunction name="setEntityName" access="private" returntype="void" output="false">
<cfargument name="entityname" required="true" hint="I am the entityname">
<cfscript>
variables.instance.entityname = arguments.entityname;
</cfscript>
</cffunction>
</cfcomponent>
What I decided to do was create a Gateway Factory (based on an idea I stole from Paul Marcotte's Metro), which would instantiate and return a CFC that I'd written, and if one didn't exist then it would create a "virtual" one in memory. This means that I only need to create gateway CFCs for entities that need more than the basic CRUD methods. My first decision was whether to search the file system for the cfc (using a naming convention to find it), or if I should define gateway CFCs that I've written in a bean factory. The later was the obvious choice as not only is it a neat solution, but I can use the Bean Factory to inject any dependancies my custom DAO/Gateway needs.
The Gateway Factory that I use actually uses ColdBox's in-built beanfactory (which is based on Lightwire) and the getModel() method, but I've chnaged the code below so that it can use Lightwire or ColdSpring without ColdBox. So here it is:
<cfcomponent hint="I am a factory for Gateway objects" output="false">
<!---
========================================================================================
Purpose: A factory for Gateway objects
Created By: John Whish
----------------------------------------------------------------------------------------
When the GatewayFactory is asked for a gateway object, it will check if the gateway is
defined as bean in the beanfactory. If found it will use it, otherwise it will create a
virtual one.
========================================================================================
--->
<!---
=======================================================================================
Instance variables
setting outside of the constructor because of DI (setters are called before constructor)
=======================================================================================
--->
<cfset variables.instance = {} />
<cfset variables.instance.gatewaymap = {} />
<!---
=======================================================================================
Public
=======================================================================================
--->
<cffunction name="init" access="public" returntype="any" hint="I am the constructor" output="false">
<cfscript>
return this;
</cfscript>
</cffunction>
<cffunction name="getGateway" access="public" returntype="model.abstract.AbstractGateway" hint="I return a gateway" output="false">
<cfargument name="entityname" required="true" hint="I am the name of the entity that we need a gateway for. If we want the 'UserGateway', then pass in 'User'">
<cfscript>
var GatewayObject = "";
var gatewaymap = getGatewayMap();
// gateway beans must be named using the convention #MyEntityName#Gateway
var gatewayname = arguments.entityname & "Gateway";
if ( StructKeyExists( gatewaymap, gatewayname ) )
{
// already exists, gateways are singletons so return it
GatewayObject = gatewaymap[ gatewayname ];
}
else
{
// is there a gateway bean matching the passed name?
if ( getBeanFactory().containsBean( gatewayname ) )
{
// gateway is defined in bean factory so get it
GatewayObject = getBeanFactory().getBean( gatewayname );
}
else
{
// gateway does not exist, so create a virtual one that exists in memory
GatewayObject = createGateway( arguments.entityname );
}
// save a reference to the gateway bean so we need to check with beanfactory again
// or create it again (especially as it's a singleton)
addGateway( gatewayname, GatewayObject );
}
return GatewayObject;
</cfscript>
</cffunction>
<!---
=======================================================================================
Private
=======================================================================================
--->
<cffunction name="addGateway" access="private" returntype="void" hint="I add a gateway to the map" output="false">
<cfargument name="entityname" required="true" type="string" hint="I am the name of the entityname">
<cfargument name="gateway" required="true" type="model.abstract.AbstractGateway" hint="I am the gateway object">
<cfscript>
variables.instance.gatewaymap[ arguments.entityname ] = arguments.gateway;
</cfscript>
</cffunction>
<cffunction name="createGateway" access="private" returntype="model.abstract.AbstractGateway" hint="I create the gateway" output="false">
<cfargument name="entityname" required="true" hint="I am the name of the entityname">
<cfscript>
return new model.abstract.AbstractGateway( arguments.entityname ) ;
</cfscript>
</cffunction>
<cffunction name="getGatewayMap" access="private" returntype="struct" hint="I return a struct of gateway objects" output="false">
<cfscript>
return variables.instance.gatewaymap;
</cfscript>
</cffunction>
<!---
=======================================================================================
Dependancies
=======================================================================================
--->
<cffunction name="getBeanFactory" access="private" returntype="coldspring.beans.BeanFactory" hint="I return the ColdSpring Bean Factory" output="false">
<cfscript>
return variables.instance.beanFactory;
</cfscript>
</cffunction>
<!--- ColdSpring looks for this method and injects itself if found --->
<cffunction name="setBeanFactory"
access="public"
returntype="void"
output="false"
hint="If Coldspring finds this method it will inject the beanfactory into this object. ">
<cfargument name="factory"
type="coldspring.beans.BeanFactory">
<cfscript>
variables.instance.beanFactory = arguments.factory;
</cfscript>
</cffunction>
</cfcomponent>
As I'm using ColdSpring, I also need a bean definition file, here is an example for a UserGateway that physically exists:
<beans>
<!-- define gateways that physically exist -->
<bean id="UserGateway" class="model.user.UserGateway">
<constructor-arg name="entityname">
<value>User</value>
</constructor-arg>
</bean>
<!-- define gatewayfactory -->
<bean id="GatewayFactory" class="model.factory.GatewayFactory">
</beans>
In my code I can do something like this (although you'd probably not want to reference the application scope directly):
<cfset GatewayFactory = application.BeanFactory.getBean( "GatewayFactory" )>
<!--- get the physical UserGateway instance --->
<cfset UserGateway = GatewayFactory.getGateway( "User" )>
<!--- get a virtual PermissionGateway instance --->
<cfset PermissionGateway = GatewayFactory.getGateway( "Permission" )>
As you can see, my calling code doesn't need to know if the the Gateway exists or not, or even if it is in the ColdSpring bean def, it just gets it. This means that if at a later date I decide that I do need a method for the PermissionGateway which isn't one of the CRUD methods, then I can just create the CFC with my new method, add it to the ColdSpring bean config and it'll work without me needing to change any of the calling code.
As I said, this is something that I'm only experimenting with and haven't used in production yet so this post comes with no warranties!
- Posted in:
- Coldbox
- OOP
- Coldspring
- Hibernate
- Lightwire


I love how Hibernate gives us a consistent API to the database. Really allows us to write some generic, but useful code.
Comment by Tony Nelson – October 08, 2009
Another reason I wanted to abstract out the hibernate stuff is so that I can add in evict() calls etc (if required) later depending on which caching strategy I use.
Comment by John Whish – October 08, 2009
Comment by Tony Nelson – October 08, 2009
Comment by John Whish – October 12, 2009
This post has given me a better insight, not only to ORM but also how to reduce code duplication.
Thanks
Comment by jbuda – November 16, 2010
Just trying out this abstract concept and really like it.
However, in the ColdSpring.xml do you not need to define the ColdSpring BeanFactory? I have everything mapped in the CFADMIN but my AbstractGateway errors saying that the variables.instance.beanFactory is empty... i assume it never gets set.
Thanks
Comment by jbuda – November 17, 2010
ColdSpring should inject itself into the Gateway Factory. If you don't enter the function metadata correctly then ColdSpring won't do this for you auto-magically. I did write another post about this:
www.aliaspooryorik.com/blog/index.cfm/e/posts.details/post/212
Comment by John Whish – November 22, 2010
Comment by Daniel – September 08, 2011
Comment by Daniel – September 09, 2011
I posted it here:
www.aliaspooryorik.com/blog/index.cfm/e/posts.details/post/307
Comment by John Whish – September 09, 2011