Object Oriented Coldfusion : 5 : The Gateway Object
Posted on September 11, 2007
One of the biggest reasons to move towards using OO concepts with Coldfusion is to build modular, extendable code libraries which are very easy to reuse throughout an application or group of applications. As a start, we’ll begin by moving queries that generally return multiple records into Gateway objects.
Changing your code perspective
If you’ve gone through my Mach-II Primer, you’ve probably started rethinking how you arrange your code. If not, let’s start with some old school, procedurally programmed Coldfusion code:
Database table CF_Contacts
CONTACT_ID | CONTACT_FIRST_NAME | CONTACT_LAST_NAME |
---|---|---|
1 | John | Smith |
2 | Jane | Doe |
3 | Juan | Gonzalez |
/contacts/contact_list.cfm
<cfquery name="qContacts" datasource="#request.DSN#">
SELECT
CONTACT_ID,
CONTACT_FIRST_NAME,
CONTACT_LAST_NAME
FROM
CF_CONTACTS
ORDER BY
CONTACT_LAST_NAME, CONTACT_FIRST_NAME
</cfquery>
<ul>
<cfoutput query="qContacts">
<li>
[ <a href="contact_form.cfm?CONTACT_ID=#qContacts.CONTACT_ID#">Edit</a> ]
<a href="contact_detail.cfm?CONTACT_ID=#qContacts.CONTACT_ID#">
#qContacts.CONTACT_LAST_NAME#, #qContacts.CONTACT_FIRST_NAME#
</a>
</li>
</cfoutput>
</ul>
This outputs something like:
- [Edit] John Smith
- [Edit] Jane Doe
- [Edit] Juan Gonzalez
The query qContacts
will usually return a record set containing more than one record. These types of queries are generally placed into a Gateway Object
.
/cfc/mysite/contacts/ContactGateway.cfc
<cfcomponent name="ContactGateway" output="false" hint="Defines Gateway functions for Contacts">
<cffunction name="init" access="public" output="false" returntype="ContactGateway" hint="constructor">
<cfargument name="DSN" type="string" required="true" hint="datasource" />
<cfset variables.DSN = arguments.DSN />
<cfreturn this />
</cffunction>
<cffunction name="getAllContacts" access="public" output="false" returntype="query"
hint="returns a query recordset of contacts">
<cfset var qContacts = "" />
<cfquery name="qContacts" datasource="#variables.DSN#">
SELECT
CONTACT_ID,
CONTACT_FIRST_NAME,
CONTACT_LAST_NAME
FROM
CF_CONTACTS
ORDER BY
CONTACT_LAST_NAME,
CONTACT_FIRST_NAME
</cfquery>
<cfreturn qContacts />
</cffunction>
</cfcomponent>
As outlined in the first four parts of this primer, the object ContactGateway.cfc has two functions:
init()
The constuctor method init()
takes a single argument which is the value of the datasource. Since the same datasource will be used for every query throughout this application, it will be placed in the variables scope
of the object.
<cfset variables.DSN = arguments.DSN />
getAllContacts()
This function wraps the same query that has been moved over from the CFM page. Notice that the name of the query has been var
scoped, making qContacts
a function local variable.
The only change to the query has been to value of the datasource. Instead of reading from the request scope of the CFM file, it’s been placed into the variables scope of the object via init()
.
<cfquery name="qContacts" datasource="#variables.DSN#">
So how do I get the data now?
Instead of writing the query on the same CFM file that will output its results, we’ll create an instance of the Gateway Object and access the query through it.
/contacts/contact_list.cfm
<cfset contactGateway = createObject("component", "cfc.mySite.contacts.ContactGateway").init( DSN = request.DSN ) />
<cfset qContacts = contactGateway.getAllContacts() />
<ul>
<cfoutput query="qContacts">
<li>
[ <a href="contact_form.cfm?CONTACT_ID=#qContacts.CONTACT_ID#">Edit</a> ]
<a href="contact_detail.cfm?CONTACT_ID=#qContacts.CONTACT_ID#">
#qContacts.CONTACT_LAST_NAME#, #qContacts.CONTACT_FIRST_NAME#
</a>
</li>
</cfoutput>
</ul>
What about user specific data?
First, let’s expand our database a bit. We’ll add a Users table and a UserContacts table.
Database
USER_ID | USER_NAME |
---|---|
1 | hpotter |
2 | rweasley |
3 | hgrainger |
[Table: Users]
(Guess which books I’ve started reading?)
CONTACT_ID | CONTACT_FIRST_NAME | CONTACT_LAST_NAME |
---|---|---|
1 | John | Smith |
2 | Jane | Doe |
3 | Juan | Gonzalez |
[Table: CF_Contacts]
This table associates Users with Contacts
USER_ID | CONTACT_ID |
---|---|
1 | 1 |
1 | 2 |
2 | 3 |
3 | 2 |
[Table: UserContacts]
Next we’ll have to update our query to bring back only Contacts associated to a specific User or all Contacts if no user is specified.
Updated getAllContacts()
<cffunction name="getAllContacts" access="public" output="false" returntype="query"
hint="returns a query recordset of contacts">
<cfargument name="USER_ID" type="numeric" required="true" default="0"
hint="Passing no value will return all Contacts" />
<cfset var qContacts = "" />
<cfquery name="qContacts" datasource="#variables.DSN#">
SELECT
a.CONTACT_ID,
a.CONTACT_FIRST_NAME,
a.CONTACT_LAST_NAME
FROM
CF_CONTACTS a
<cfif arguments.USER_ID neq 0>
LEFT JOIN
USERCONTACTS b
ON
b.CONTACT_ID = a.CONTACT_ID
AND
b.USER_ID = <cfqueryparam value="#arguments.USER_ID#" cfsqltype="cf_sql_integer" />
</cfif>
ORDER BY
a.CONTACT_LAST_NAME,
a.CONTACT_FIRST_NAME
</cfquery>
<cfreturn qContacts />
</cffunction>
Finally, we need to change how we’re requesting this data. Let’s assume that the currently logged in user’s UserID is stored in the variable session.userID.
Retrieving user specific data
<cfparam name="session.userID" type="numeric" default="0" />
<cfset contactGateway = createObject("component", "cfc.mySite.contacts.ContactGateway").init( DSN = request.DSN ) />
<cfset qContacts = contactGateway.getAllContacts( USER_ID = session.userID ) />
So if Mr. Potter is logged into the site, the page should only output
- [ Edit ] John Smith
- [ Edit ] Jane Doe
You could easily reuse the same code in an Administrative section of the application, where one user can control others, as well as alter records associated to them. If you had a list of Employees, you could pass the EmployeeID through a URL variable to the same function in order to get their contacts.
Administrative task
<cfparam name="url.EmployeeID" type="numeric" default="0" />
<cfset contactGateway = createObject("component", "cfc.mySite.contacts.ContactGateway").init( DSN = request.DSN ) />
<cfset qContacts = contactGateway.getAllContacts( USER_ID = url.EmployeeID ) />
Reducing calls to createObject()
As the code stands now, the ContactGateway.cfc object is created every time the page is requested and placed into the variables
scope of the CFM file. Also, this object is used by every user of the application, but there is no user specific data stored in the variables
scope of the CFC file.
Rather than creating the object over and over, it can be created once and placed into the application
scope. This places the object in memory for the life of the application, reducing processing time and the overall memory usage of the application.
The simplest place to do this is in the Application.cfc file inside the onApplictionStart method. This will ensure that the object is created and placed in the application scope when the application is loaded for the first time.
Application.cfc : onApplicationStart()
<cfset application.DSN = "myData" />
<cfset application.contactGateway = createObject("component", "cfc.mySite.contacts.ContactGateway")
.init( DSN = application.DSN ) />
Now we can remove yet another line of code from our CFM file:
/contacts/contact_list.cfm
<cfset qContacts = application.contactGateway.getAllContacts() />
<!--- or --->
<cfparam name="session.userID" type="numeric" default="0" />
<cfset qContacts = application.contactGateway.getAllContacts( USER_ID = session.userID ) />
I can’t say that you should place a Gateway object into the application scope 100% of the time, but I can say that you should do it more often than not.
What does this accomplish?
Instead of a single file containing a query and HTML, we now have two files with even a little more code than when we started.
By splitting the code into two files, we gain two things:
- There is a reduction of the amount of code in a file that comprises part of your application’s user interface (UI), a file that is commonly referred to as a
View
. - The query is now available to the application as a reusable chunk of code via a Gateway object, making it part of the application’s
Model
.
Placing the object into application memory drastically reduces page load times and helps to decrease the overall footprint of the application.
Even if you don’t use a published framework like Mach-II, Model-Glue or Fusebox to develop your application, separating code that belongs in the Model from code that manages the View can not only simplify your initial development process, it can reduce future development as well.
Next up
We can handle multiple records in the Model, now we need to deal with them one at a time. Next we chase down the elusive Bean Object.
Adrian J. Moreno
Adrian is a CTO and solution architect specializing in software modernization. More information