i Know Kung Foo Consulting

Using a Session Facade to handle evolving session variables

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 Design Pattern is basically 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.userIDsession.user.getUserID()
session.policyIDsession.policy.getPolicyID()
session.agentIDsession.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:

<cfset theDSN = "someDatasource" />
<cfset theSessionFacade = createObject("component", "SessionFacade").init() />

<cfset application.FooGateway = createObject("component", "FooGateway").init( DSN = theDSN, SessionFacade = theSessionFacade) />

So what happens when you call application.FooGateway.getRecords()?

  1. You get the set of records specific to the current user in session.
  2. You get the same set of records for every user.
  3. 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.">
      <cfset var foo = "" />
      <cfquery name="foo" datasource="#variables.DSN#">
         SELECT foo, wibble, narf
         FROM someTable
         WHERE userID = #variables.SessionFacade.getUser().getUserID()#
      </cfquery>
      <cfreturn 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.


Comments (Comment Moderation is enabled. Your comment will not appear until approved.)
jM's Gravatar Informative post. I _thought_ I understood it until I read your response here ;)
http://www.mail-archive.com/cfcdev@cfczone.org/msg...

&lt;quote&gt;
..Now we can safely output #request.SessionFacade.getUser.get*()# anywhere on the page. The session.user object is read using a lock inside SessionFacade.getUser().
&lt;/quote&gt;

Can you explain how locking only SessionFacade.getUser() is safe? Shouldn't the facade also use locking when calling methods of the session.user object, like the facade in this post?

&lt;cffunction name=&quot;getUserID&quot; returntype=&quot;numeric&quot; hint=&quot;Logged-in user's ID&quot;&gt;
..
&lt;cflock scope=&quot;session&quot; type=&quot;readonly&quot; timeout=&quot;1&quot;&gt;
&lt;!--- New object variable ---&gt;
&lt;cfset userID = session.user.getUserID() /&gt;
&lt;/cflock&gt;
..
&lt;/cffunction&gt;
# Posted By jM | 3/17/07 4:06 PM
Adrian J. Moreno's Gravatar SessionFacade.getUser() uses a read-only lock to return session.user. Since the whole object has been returned under a lock, there's no need to place locks around the individual getters.
# Posted By Adrian J. Moreno | 3/19/07 11:35 PM
jM's Gravatar Then why not just use SessionFacade.getUser().getUserID() in FooGateway ? Or am I still missing something .. :)?
# Posted By jM | 3/20/07 2:01 PM
Adrian J. Moreno's Gravatar You aren't missing anything. I was. (D'oh!)

That last section of code had

WHERE userID = #variables.SessionFacade.getUserID()#

and has been updated to

WHERE userID = #variables.SessionFacade.getUser().getUserID()#

Sorry about that.
# Posted By Adrian J. Moreno | 3/20/07 3:17 PM
jM's Gravatar Good to know I may actually have a _few_ working brain cells left after all ;)
# Posted By jM | 3/20/07 3:30 PM
jM's Gravatar Btw - I just wanted to say thanks for writing this entry. I was definitely struggling with the whole session facade concept until I read it. Most of the articles I found were a little too abstract. This consise and concrete example really hit the spot!

Cheers
# Posted By jM | 3/20/07 3:45 PM