Photo from Chile

How I Use Transfer Today - Encapsulating Database Access

I've been meaning to follow up on my How I Use Transfer series, as I've made a few changes to the way I write my ColdFusion code since that series was written. One of the biggest changes was to encapsulate all database access in my Gateway components.

Now I say biggest not because it took a lot of time and effort to make the change, in fact the opposite is true. I say biggest simply because it represented a significant shift in the way I'm designing my model. Let me start with the rationale for making this change.

A lot of my design decisions are based on encapsulation, and I find the object oriented principle "encapsulate what varies" to be a good guiding principle. I tend to ask myself, "Self, if something were to change, how many components would have to change as a result?" Ideally the answer to that question would be "one" (notice I said ideally). So, regarding database access, what can change?

  • Our underlying database could change. For example, moving from SQL Server to MySQL.
  • Our database structure could change. For example, adding/modifying tables, columns, etc.
  • If we're using an ORM, our ORM could change. For example, moving from Transfer to a different ORM (heresy!).

Many of us already use gateways to address the first two issues. We put all of our queries, written in either SQL or TQL into gateways because, if something were to change about our database we would like to be in a position where we only have to change the Gateway. In a large part because of comments made by Brian Kotek, I came to the realization that, as long as I'm attempting to encapsulate database access, I should really encapsulate access to the ORM as well.

So, what does that mean in terms of Transfer? It means that the only place I should be putting any references to Transfer is in a Gateway. That means no references to Transfer in my Controllers (not that there ever was, but I just thought I'd point it out as a rule), no references to Transfer in my Services, and no references to Transfer in my Decorators. The only component that ever talks to Transfer is the Gateway.

Now, what makes this fairly simple to implement is the fact that I can simply inject the gateway into my Service and into my Decorator, so I can pretty much do exactly what I was doing before, but instead of talking to Transfer I talk to a Gateway. Another thing that makes it simple for me is that I'm using abstract objects, so for the most part my code only has to change in the abstract objects (i.e., only one object has to change), rather than requiring me to make changes to dozens of concrete objects.

OK, time for some examples. I'm going to take a before and after approach, looking at code from my previous Transfer series and code as I'm writing it today. Let's start with the get() method in my Abstract Service object. Here's the old version:

view plain print about
1<cffunction name="get" access="public" returntype="any">
2    <cfargument name="theId" type="any" required="yes" />
3    <cfargument name="needsClone" type="any" required="false" default="false" />
4    <cfargument name="args" type="any" required="no" default="" />
5    <cfargument name="TransferClassName" type="any" required="no" default="#getTransferClassName()#" />
6    <cfargument name="FieldList" type="any" required="no" default="" />
7    <cfset var theTO = 0 />
8    <cfif arguments.theId EQ 0>
9        <cfset theTO = getTransfer().new(arguments.TransferClassName) />
10    <cfelse>
11        <cfset theTO = getTransfer().get(arguments.TransferClassName,arguments.theId) />
12        <cfif arguments.needsClone>
13            <cfset theTO = theTO.clone() />
14        </cfif>
15    </cfif>
16    <!--- If an args struct was passed in, use it to populate the TransferObject --->
17    <cfif IsStruct(arguments.args)>
18        <cfset theTO.populate(arguments.args,arguments.FieldList) />
19    </cfif>
20    <cfreturn theTO />
21</cffunction>

And here's the new version:

view plain print about
1<cffunction name="get" access="Public" returntype="any"
2    hint="I return a Business Object, ready to be used.">

3    <cfargument name="theId" type="any" required="yes" />
4    <cfargument name="readOnly" type="any" required="false" default="true" />
5    <cfargument name="args" type="any" required="no" default="" />
6    <cfargument name="Result" type="any" required="no" default="" />
7    <cfargument name="MainObjectName" type="any" required="no" default="#getMainObjectName()#" />
8    <cfargument name="FieldList" type="any" required="no" default="" />
9    <cfset var theTO = getTheGateway().get(arguments.theId,arguments.readOnly,arguments.MainObjectName) />
10    <!--- If an args struct was passed in, use it to populate the TransferObject --->
11    <cfif IsStruct(arguments.args) AND IsObject(arguments.Result)>
12        <cfset theTO.populate(arguments.args,arguments.Result,arguments.FieldList) />
13    </cfif>
14    <cfreturn theTO />
15</cffunction>

So I've moved all of the logic that has anything to do with Transfer into the Gateway, thereby encapsulating access to the ORM. Just ignore the Result stuff, that's part of another aspect to my new approach, which also makes use of my validation framework. You can probably guess what's inside the Gateway's get() method, but let's take a look anyway:

view plain print about
1<cffunction name="get" access="Public" returntype="any">
2    <cfargument name="theId" type="any" required="yes" />
3    <cfargument name="readOnly" type="any" required="false" default="true" />
4    <cfargument name="MainObjectName" type="any" required="no" default="#getMainObjectName()#" />
5    <cfset var theTO = 0 />
6    <cfif (IsNumeric(arguments.theId) AND NOT Val(arguments.theId)) OR NOT Len(arguments.theId)>
7        <cfset theTO = getTransfer().new(arguments.MainObjectName) />
8    <cfelse>
9        <cfset theTO = getTransfer().get(arguments.MainObjectName,arguments.theId) />
10        <cfif NOT arguments.readOnly>
11            <cfset theTO = theTO.clone() />
12        </cfif>
13    </cfif>
14    <cfreturn theTO />
15</cffunction>

So that was a pretty quick and easy refactor. Let's take a look at a couple of the methods in my Abstract Transfer Decorator. Here's the old version:

view plain print about
1<cffunction name="save" access="public" returntype="void">
2    <cfset getTransfer().save(this) />
3</cffunction>
4
5<cffunction name="delete" access="public" returntype="void">
6    <cfset getTransfer().delete(this) />
7</cffunction>

And here's the new version:

view plain print about
1<cffunction name="save" access="public" returntype="void">
2    <cfargument name="Result" type="any" required="true" />
3    <cfargument name="Context" type="any" required="false" default="" />
4    <cfset validate(arguments.Result,arguments.Context) />
5    <cfif arguments.Result.getIsSuccess()>
6        <cfset getTheGateway().save(this) />
7        <cfset Result.setSuccessMessage("OK, the #variables.myInstance.ObjectDesc# record has been saved.") />
8    </cfif>
9</cffunction>
10
11<cffunction name="delete" access="public" returntype="void">
12    <cfset getTheGateway().delete(this) />
13</cffunction>

Again, it's pretty obvious that I've simply moved the Transfer "stuff" into the Gateway. All of the extra stuff in my save() method (i.e., Result, validate and Context) have to do with the new way that I've implemented validations using my framework. Let's take a look at the save() and delete() methods in the Abstract Gateway:

view plain print about
1<cffunction name="save" access="public" returntype="void">
2    <cfargument name="theTO" type="any" required="true" />
3    <cfset getTransfer().save(arguments.theTO) />
4</cffunction>
5
6<cffunction name="delete" access="public" returntype="void">
7    <cfargument name="theTO" type="any" required="true" />
8    <cfset getTransfer().delete(arguments.theTO) />
9</cffunction>

Another quick and easy refactor, allowing me to achieve my objective without having to write or change much code at all. There are other specific cases where I was referencing Transfer directly in my Services and Decorators, and I've moved most of those into the Gateways as well.

Let me close by saying that I am not putting this forward as a recommendation or best practice. This just happens to be the way that I'm doing it, and I hope that I've made my reasons clear. I do accept that I'm adding an extra layer of abstraction to my model, which is in fact my intention, and I can totally see the argument that some may not wish to add this, and that it's much simpler to just talk to Transfer via Services and Decorators. That's one of the great things about software development, there are always many good solutions to any given problem.

TweetBacks
Comments
Hi Bob, this really is an excellent series and has been really interesting reading. I don't suppose you have a sample app that we could download? I'm really interested to see how you use you organise your classes as a package/business area.

I've also been inspired by your posts to use the transfer meta data to building an abstract validation method. I had been using class metadata but now that you've introduced me to the getPropertyIterator() method it's opened up a whole new world!
# Posted By John Whish | 12/9/08 10:45 AM
Hi John,

A sample app is something that I'd like to build - it's just a question of finding the time to do it. The closest thing I have to a sample app right now is the demo app that is included with the ValidateThis download.

Speaking of validations, I read your blog post and it looks like you're doing some interesting stuff. I encourage you to take a look at ValidateThis as well - it is doing some similar things, but with even more encapsulation and some additional functionality (e.g., client-side validations). I'm loathe to advertise my own stuff, but hey, we're having this discussion on my blog ;-)

Even if you're not interested in using ValidateThis as is, you may find some interesting ideas by looking at the codebase, which is actually quite simple. For example, if you prefer to define your business rules in cfproperty tags rather than in an xml file, it would be quite easy to extend ValidateThis to get its metadata that way. Let me know if you're interested in investigating that route and maybe we can look at it together.
# Posted By Bob Silverberg | 12/9/08 11:02 AM
Hi Bob, I definitely going to be having a look at ValidateThis!

Glad you read the post, the idea of using meta data to drive as much as possible really appeals to me. Transfer, DataMgr and my own "half-baked" DAL allow me get the information about the data structure (whether via introspection or defined XML) so it just makes sense to use it for basic validation. I would be interested in investigating taking ValidateThis down that route :)
# Posted By John Whish | 12/9/08 11:14 AM
Bob, hope things are going well with you.

I came back to this series to find out exactly how you go about injecting your individual concrete business objects with the corresponding gateway? Reason I'm asking is that I need to refactor some database calls that are currently present in my concrete business objects into a separate gateway object. But I'm getting lost in where in the instantiation process these business objects are given concrete gateways. This is all in the effort to avoid the "anemic domain model" syndrome.

Thanks!
# Posted By Bim Paras | 10/14/09 8:04 PM
Never mind Bob. Right after I hit submit, I found your follow-up article:
http://www.silverwareconsulting.com/index.cfm/2008...

Doh!
# Posted By Bim Paras | 10/14/09 8:14 PM