Sunday, August 2, 2009

Hibernate, ColdFusion 9, and DAOs

It's been a long weekend, so this post might seem a little scattered and more-or-less random thoughts. Bear with me.

Since Hibernate gives us a consistent interface for data access, most of the Data Access Objects (DAOs) that we write in CF9 will/should look pretty similar. With that in mind, I've been playing around with creating a generic Data Access Object using simple code generation that could be used and decorated by all persistent objects in the application.

The general idea is that all DAOs are generated by a DAO Factory using a skeleton CFC as a template and basic keyword replacement. The factory would then write the DAOs to disk, where the newly created components would be instantiated and returned to ColdSpring as part of a factory-method.

Here's what my ColdSpring definition looks like:


<beans>

<bean id="userService" class="app.services.UserService" />

<bean id="userDAO" factory-bean="daoFactory" factory-method="getUserDAO" />

<bean id="daoFactory" class="utils.orm.DAOFactory">
<constructor-arg name="relativePath">
<value>/app/proxy/dao</value>
</constructor-arg>
</bean>

</beans>


You can even extend the generated DAO by defining the bean like this:


<bean id="userDAO" factory-bean="daoFactory" factory-method="getUserDAO">
<constructor-arg name="extends">
<value>app.model.user.UserDAO</value>
</constructor-arg>
</bean>



The factory would use the onMissingMethod event handler to determine the correct entity to load by stripping "get" and "DAO" from the factory-method being called. In this case, the entity would be "User". The factory would then do really simple text replacement in the DAO template CFC to generic the UserDAO.

Here's what my DAO template looks like:


<cfcomponent>

<cffunction name="get" access="public" output="false" returntype="any">
<cfargument name="id" required="true" />

<cfreturn EntityLoadByPK("${Entity}",arguments.id) />

</cffunction>

<cffunction name="save" access="public" output="false" returntype="void">
<cfargument name="${entity}" required="true" />

<cfset EntitySave(arguments.${entity}) />

</cffunction>

<cffunction name="delete" access="public" output="false" returntype="void">
<cfargument name="${entity}" required="true" />

<cfset EntityDelete(arguments.${entity}) />

</cffunction>

</cfcomponent>


And here's what the final UserDAO looks like:


<cfcomponent>

<cffunction name="get" access="public" output="false" returntype="any">
<cfargument name="id" required="true" />

<cfreturn EntityLoadByPK("User",arguments.id) />

</cffunction>

<cffunction name="save" access="public" output="false" returntype="void">
<cfargument name="user" required="true" />

<cfset EntitySave(arguments.user) />

</cffunction>

<cffunction name="delete" access="public" output="false" returntype="void">
<cfargument name="user" required="true" />

<cfset EntityDelete(arguments.user) />

</cffunction>

</cfcomponent>


Hopefully that makes some sense. I haven't fully tested everything yet, but the theory is there. The next step would be to update the DAO template to add some more complex logic, like adding validation and leveraging CF9's new caching enhancements. Granted you could always use ColdSpring's AOP to add caching, but that's a different topic.

If anyone is interested in seeing more of the code, let me know.

4 comments:

  1. This is really good stuff!! Thank you! How does gateway CFC's fit into this and the hibernate application?

    ReplyDelete
  2. Would you have a function in the user DAO that would create the entity for you? Maybe like this:

    ReplyDelete
  3. The code got stripped out

    cffunction name="create" access="public" output="false" returntype="any"

    cfreturn EntityNew( "member" )

    cffunction

    ReplyDelete
  4. Joshua,

    In my example, you wouldn't really need a gateway component. But yes, you could update the DAO.cfc to include a create method that returned a new instance of the entity. Inside your userService, you could then do something like "var user = userDAO.create();". Hopefully that makes sense.

    Since this post I've taken a different approach to ORM and instead created the ColdMVC framework, but I think the concepts are still valid.

    ReplyDelete