Creating Gateways using a GatewayFactory for ORM

October 08, 2009

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!


4 comments

  1. I've been playing around with a similar idea to use a factory to return generic objects: bears-eat-beets.blogspot.com/2009/10/generic-dao-factory-for-coldfusion-9.html

    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
  2. @Tony, nice post and as you say a similar concept. I tend not to use onMissingMethod because I like having an API, but for RAD and prototyping it's really cool :)

    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
  3. Yeah I don't like using onMissingMethod either due to the lack of an API, but in my example it's not really a problem since the only components that use onMissingMethod are the factories, which are self-contained within ColdSpring. The objects that my application really cares about, the services and DAOs, still have a clean API.

    Comment by Tony Nelson – October 08, 2009
  4. I've tried the hybrid approach before where the service only had concrete methods and the gateways etc used onMissingMethod but I was never that comfortable with it. Maybe I should revisit it.

    Comment by John Whish – October 12, 2009

Leave a comment

If you found this post useful, interesting or just plain wrong, let me know - I like feedback :)

Please note: If you haven't commented before, then your comments will be moderated before they are displayed.