Using a Session Facade to handle evolving session variables
Posted on February 12, 2007
Last year I started at a company where two major projects were underway: Create a new payment application using Mach-II, then Redesign and overhaul the website. The payment application would have to work in both the current and upcoming versions of the website.
The Situation
The current site is your standard, procedurally programmed CF site with some Mach-II applications and was developed over the last 6 years. It contains a lot of inline queries and business logic. The website redesign meant updating the look of the site, but it also gave us the opportunity to update some of the legacy CF code and add new functionality.
The plan for the new site was to move all queries and much of the business logic into Coldfusion Components (CFCs). We also wanted to implement an Object Oriented Architecture to handle some of the data that was common across applications.
We knew how the session variables were defined now, but how do we build an application without knowing what the session variables will look like down the road?
The Solution
Since we knew the new site would change how session data is handled, we developed the payment application using a Session Facade
. A Facade
is a design pattern, a common solution to a recurring problem. Click here if you want your head to explode or continue reading and hopefully I can clear things up.
Here are how the session variables look in each version of the website:
Old | New |
---|---|
session.userID | session.user.getUserID() |
session.policyID | session.policy.getPolicyID() |
session.agentID | session.user.getAgentID() |
A Session Facade gives us a single point of reference for any session variable, regardless of its actual variable name.
<cfcomponent name="SessionFacade">
<cffunction name="init" returntype="SessionFacade">
<cfreturn this />
</cffunction>
<cffunction name="getUserID" returntype="numeric" hint="Logged-in user's ID">
<cfset var userID = "" />
<cflock scope="session" type="readonly" timeout="1">
<!--- Old simple variable --->
<cfset userID = session.userID />
</cflock>
<cfreturn userID />
</cffunction>
</cfcomponent>
All we had to do was change what these methods returned and the payment application plugged right into the new architecture.
<cfcomponent name="SessionFacade">
<cffunction name="init" returntype="SessionFacade">
<cfreturn this />
</cffunction>
<cffunction name="getUserID" returntype="numeric" hint="Logged-in user's ID">
<cfset var userID = "" />
<cflock scope="session" type="readonly" timeout="1">
<!--- New object variable --->
<cfset userID = session.user.getUserID() />
</cflock>
<cfreturn userID />
</cffunction>
</cfcomponent>
Most of the new programming centered on updating the user interface. The UI for the payment application was originally built using CSS, so there was little more to do than swapping out class names.
For the new website, we implemented a base Session Facade for common session values and extended it for specific applications as needed. As the project progressed, whenever we made alterations to the core session objects, all we had to do was update the methods in the base Session Facade and almost no alterations were then needed in any of the dozen+ applications that shared our new data architecture.
The Pitfalls
Commonly used objects are often placed in the application scope in order to cut down on memory usage. However, since the SessionFacade references values specific to the current user, you have to be very aware of the variables scope
when using it or objects that require it.
Let’s look at this Gateway that’s been created and placed into the Application scope. It requires the SessionFacade to be passed into the init()
method in order to be created. Since we only need to get the UserID
in session as part of a query, let’s cut down on the amount of typing we’ll have to do later and just put the UserID in the variables scope.
<cfcomponent name="FooGateway">
<cffunction name="init" returntype="FooGateway">
<cfargument name="DSN" required="true" type="string" />
<cfargument name="SessionFacade" required="true" type="SessionFacade" />
<cfset variables.DSN = arguments.DSN />
<cfset variables.userID = arguments.SessionFacade.getUserID() />
<cfreturn this />
</cffunction>
<cffunction name="getRecords" returntype="query" hint="Get records for the logged in user.">
<cfset var foo = "" />
<cfquery name="foo" datasource="#variables.DSN#">
SELECT foo, wibble, narf
FROM someTable
WHERE userID = #variables.userID#
</cfquery>
<cfreturn foo />
</cffunction>
</cfcomponent>
Now let’s create this Gateway in the application scope:
theDSN = "someDatasource";
theSessionFacade = new SessionFacade.init();
application.FooGateway = new FooGateway.init( DSN = theDSN, SessionFacade = theSessionFacade);
So what happens when you call application.FooGateway.getRecords()
?
- You get the set of records specific to the current user in session.
- You get the same set of records for every user.
- Your helpdesk manager throws a phone at your head.
Number 2 will definitely happen with number 3 depending on the distance between your and your helpdesk manager’s desks.
Since the init()
method is only called when the object is created, you’ve placed the value of the current userID
and only ever that UserID into the variables scope. This static value is then passed to the query, so whatever user was in session when the gateway was created is the user ID whose records are being displayed to every other user (which explains the flying phone).
Do the extra typing to ensure you’re passing a dynamic reference to the UserID in session:
<cfcomponent name="FooGateway">
<cffunction name="init" returntype="FooGateway">
<cfargument name="DSN" required="true" type="string" />
<cfargument name="SessionFacade" required="true" type="SessionFacade" />
<cfset variables.DSN = arguments.DSN />
<cfset variables.SessionFacade = arguments.SessionFacade />
<cfreturn this />
</cffunction>
<cffunction name="getRecords" returntype="query" hint="Get records for the logged in user.">
<cfquery name="local.foo" datasource="#variables.DSN#">
SELECT foo, wibble, narf
FROM someTable
WHERE userID = #variables.SessionFacade.getUser().getUserID()#
</cfquery>
<cfreturn local.foo />
</cffunction>
</cfcomponent>
The Return on Investment (ROI)
Using the Session Facade in the payment application decreased the amount of time for the initial development and it severely decreased the amount of time spent to integrate it with the new site.
As development on the new site progressed, we discovered better ways of managing data and objects. By creating a User Facade, we were able to streamline the process of loading different types of users down to SessionFacade.setUser()
.
We even solved the problem of replicating objects in session when running multiple instances of Coldfusion.
As the company expands and we add new features to the site, the base and application-specific Session Facades will allow us to more rapidly update and extend the shared session objects with little need for updates to existing application code.
Adrian J. Moreno
Adrian is a CTO and solution architect specializing in software modernization. More information