Ask Ben: Environment-Based Application.cfc Settings

Posted August 10, 2007 at 7:57 PM

Tags: Ask Ben, ColdFusion

Let's say I have two instances of a web site: Development and Production. On the development site, I want the sessionTimeout to be one minute. The applicationTimeout should be 2 minutes. In production, the sessionTimeout should be 20 minutes and the applicationTimeout should be 2 days. I don't want to put these values directly in Application.cfc, because I wouldn't be able to copy from Development to Production without having to remember to make that change. (I also don't want to use an ANT script at this time, either.) .... Basically, I want to set custom timeout settings in the constructor, then set application-scope settings, one time, in the onApplicationStart() method. I'm looking at using an XML file for the primary settings, so I want to avoid parsing an XML file for each request .... I would rather this be controlled by the Config file(s). I don't want my code to assume the current mode.

I'm not sure how to best answer this question. I've not often used environment-based Application.cfc settings before; I have had environment-based Application settings, but these are specific to the ColdFusion application once it has been created and associated to current page request. As such, I am not sure what the best solution is, but from what the question describes (and from what I believe) the "Best" solution would entail:

  • The ability to blindly copy the entire web-directory from one server to another server (no code changes or selective file transfers) without anything breaking. If you can't blindly copy and entire application from one server to another server without something breaking, to me, this is a serious architectural flaw (one that I have been guilty of in the past - yikes!).
  • Not storing any files outside of the web root. Not everyone has access to anything outside of the web root, so why even use that in a solution?

Based on those assumptions, the code must perform some sort contextual logic (like checking the server name or the host machine or something). So the question then becomes, where does this logic go? To answer this, we have to fully understand the life-cycle of the Application.cfc ColdFusion component:

  1. Application.cfc get created by the ColdFusion application server.
  2. Application.cfc pseudo constructor gets executed (this is the code that exists inside of the CFComponent tags but outside of the CFFunction tags).
  3. Application.cfc settings associate ColdFusion component and current page request with a new or existing ColdFusion Application memory space.
  4. ColdFusion server calls application-level events on Application.cfc if they exist.

From this, we can see that we can't cache environment-based Application.cfc settings in the APPLICATION scope; the APPLICATION scope doesn't even exist or become associated with the current page request until after the Application.cfc settings are set. Therefore, we are going to have to perform the context-based setting logic for every request and it has to be part of steps 2 or 3 above.

One option is to include a ColdFusion template into the Application.cfc file, mixin style like this:

 Launch code in new window » Download code as text file »

  • <cfcomponent>
  •  
  • <!--- Set settings. --->
  • <cfinclude template="./config/settings.cfm" />
  •  
  • <!--- ... Set APPLICATION.CFC Settings ... --->
  •  
  • <!--- ... Define Event Methods ... --->
  •  
  • </cfcomponent>

This works fine, but to be honest, I have never felt very comfortable including files into a ColdFusion component (other than in the OnRequest() event handler of course). I know it is perfectly valid, it just never sits quite right with me. Additionally, I am also not a big fan of calling code before setting the Application.cfc. I do this when determining whether the current page request is a human or a Spider/Bot/Robot, but I might rethink how that is done based on the solution I'm exploring right now.

So, as a sort of compromise, I suggest that we create an Application.cfc-scoped method that will configure and return the Application.cfc settings. On the first method request, it will create and cache the settings structure and return in. Then, all subsequent method requests will simply return the cached setting structure. This gives us the ability to:

  • Separate out the settings usage from their logic.
  • Have our Application.cfc settings be the first thing in the ColdFusion component.
  • Satisfy our "best" solution criteria outlined above.

Here is what the Application.cfc ColdFusion component might look like:

 Launch code in new window » Download code as text file »

  • <cfcomponent
  • output="false"
  • hint="Executes application-level event handlers.">
  •  
  • <!--- Set up the application. --->
  • <cfset THIS.Name = Config().Name />
  • <cfset THIS.ApplicationTimeout = Config().ApplicationTimeout />
  • <cfset THIS.SessionTimeout = Config().SessionTimeout />
  •  
  • <!--- Set up page settings. --->
  • <cfsetting
  • showdebugoutput="#Config().ShowDebugOutput#"
  • requesttimeout="#Config().RequestTimeout#"
  • />
  •  
  •  
  • <cffunction
  • name="Config"
  • access="public"
  • returntype="struct"
  • output="false"
  • hint="Returns the Application.cfc configuration settings struct based on the execution environment (production, staging, development, etc).">
  •  
  • <!---
  • Check to see if the app config structure
  • exists within this ColdFusion component yet;
  • if it doesn't we have to created it.
  • --->
  • <cfif NOT StructKeyExists( THIS, "$Config" )>
  •  
  • <!---
  • Create the empty $AppCongif struct. I am
  • starting it off with "$" to minimize the
  • chances of it conflicting with an existing
  • variable (or soon to be existing variabel).
  • --->
  • <cfset THIS[ "$Config" ] = StructNew() />
  •  
  • <!---
  • The applicaiton configuration object does
  • not yet exist. Let's create it based on the
  • server name.
  • --->
  • <cfif REFind( "(?i)swoop", CGI.server_name )>
  •  
  • <!--- Set development environment. --->
  • <cfset THIS[ "$Config" ].IsLive = false />
  • <cfset THIS[ "$Config" ].Name = "BenNadel-DEV" />
  • <cfset THIS[ "$Config" ].ApplicationTimeout =
  • CreateTimeSpan( 0, 0, 1, 0 ) />
  • <cfset THIS[ "$Config" ].SessionTimeout =
  • CreateTimeSpan( 0, 0, 1, 0 ) />
  •  
  • <!--- Page settings. --->
  • <cfset THIS[ "$Config" ].ShowDebugOutput = true />
  • <cfset THIS[ "$Config" ].RequestTimeout = 20 />
  •  
  • <cfelse>
  •  
  • <!--- Set production environment. --->
  • <cfset THIS[ "$Config" ].IsLive = true />
  • <cfset THIS[ "$Config" ].Name = "BenNadel-PROD" />
  • <cfset THIS[ "$Config" ].ApplicationTimeout =
  • CreateTimeSpan( 0, 0, 10, 0 ) />
  • <cfset THIS[ "$Config" ].SessionTimeout =
  • CreateTimeSpan( 0, 0, 10, 0 ) />
  •  
  • <!--- Page settings. --->
  • <cfset THIS[ "$Config" ].ShowDebugOutput = false />
  • <cfset THIS[ "$Config" ].RequestTimeout = 20 />
  •  
  • </cfif>
  •  
  • </cfif>
  •  
  •  
  • <!---
  • ASSERT: Whether we are calling this function
  • for the first time of the Nth time, we now
  • have the $Config object properly stored
  • in the THIS scope of the Application.cfc
  • --->
  •  
  •  
  • <!--- Return the config object. --->
  • <cfreturn THIS[ "$Config" ] />
  • </cffunction>
  •  
  • </cfcomponent>

Notice that we are setting both the Application.cfc settings and the page request settings (CFSetting) by referencing the Application.cfc-scoped Config() method and treating it as if it were a struct. Then, all of our configuration logic can go in the Config() method, neatly encapsulated and environmentally-specific.

Right now, I feel that this is the most elegant solution that satisfies all of the "Best" requirements laid out above, but of course, I am open to feedback. This is especially true as this is not production-tested (all theory baby!). I am certainly glad that this is NOT using XML. Remember, due to the Application.cfc life cycle, we cannot cache this data. Therefore, it has to run on every single page request. Why add the overhead of parsing an XML file thousands of times a day when there are other solutions? That would be foolish (even for the people who live and die fighting for XML config files).

Download Code Snippet ZIP File

Comments (12)  |  Post Comment  |  Ask Ben  |  Permalink  |  Other Searches  |  Print Page




Adobe ColdFusion 8.0.1 Update - Helping Programmers To Be Signifanctly Less Girlie - Download ColdFusion 8 Update 8.0.1 Now.

Reader Comments

Interesting... would this config() function be available through out your application, so you could use Config().dsn as a datasource in queries, and for other various app settings that change from dev to production?

Would you need to call it as Application.config() ?

Posted by Ryan on Aug 10, 2007 at 11:39 PM


@Ryan,

This type of configuration information would be purely for the Application and Page Request settings. That is the only type of configuration that might need to be contextually different from page to page.

Something like DNS is created once and then hopefully cached in the APPLICATION scope or one of its properties. There is a subtle but extremely important difference in the two different types of application "configuration". One is defining the application in the context of the ColdFusion application server (Config()) and one is defining the properties of the current ColdFusion Application.

Posted by Ben Nadel on Aug 11, 2007 at 5:10 PM


@Ryan,

Sorry, and just to clearly answer your question, No, you would not use this anywhere else in the application.

Posted by Ben Nadel on Aug 11, 2007 at 5:11 PM


Ben, this is a great example of what I was needing for a new application. Although there are some "specific" settings in the Application.cfc file, itself, it does eliminate the need for an external Application.cfc configuration file just to set the timeout values and name. (As you mention, it would be pointless in using a cfinclude just to do that simple task.)

This will work great for the example of copying from development to test to production, however, it will require a few edits when copying to a different web site, altogether. However, "some" file would need to be changed, anyway.

I don't expect that I will sell the application I'm building, but I may copy it to a different school corporation's web site currently maintained by my dad. So, I can handle a few minor edits when copying to a new web site.

I think you have given me a great starting point with your example.

Thanks!

Posted by Michael Dawson on Aug 11, 2007 at 11:03 PM


@Michael,

That's awesome. Glad I could be some help on this matter.

Posted by Ben Nadel on Aug 12, 2007 at 11:11 AM


Quite different approarch is to deploy application to various environmens with Ant script and use that script to customize environment specific variables.

Check Ant manual for Replace / ReplaceFilter tasks for details.

Posted by Tero Pikala on Aug 12, 2007 at 1:33 PM


@Tero,

I don't know much about ANT at all, but how does it deal with the fact that I have to use a specific FTP program to get stuff to the production server?

Posted by Ben Nadel on Aug 13, 2007 at 7:30 AM


@Ben,

Ant has external task for FTP communications. As long as you don't have anything really tricky it should be possible to script FTP transfer with Ant.

Check http://ant.apache.org/manual/OptionalTasks/ftp.html for all the options it has.

It can be as easy as this:

<ftp server="${ftp.host}"
remotedir="${ftp.basedir}/${ftp.subdir}"
userid="${ftp.username}"
password="${ftp.password}">
<fileset dir="${build.dir}"/>
</ftp>

build.dir is temporary directory on local machine I'm using in this case to compile Flex application to. Something similar should be done with CF code that is modified with Ant.

Posted by Tero Pikala on Aug 13, 2007 at 7:50 AM


Interesting stuff. I should really look into ANT sometime. We use a secure FTP connect that is pretty strict (I don't know much about FTP), but it would be cool to automate as much as possible.

Posted by Ben Nadel on Aug 13, 2007 at 1:33 PM


To use SFTP you need to use SCP task with Ant - and it's definitely good idea to use something else than stone age FTP without any security at all.

Posted by Tero Pikala on Aug 13, 2007 at 2:41 PM


Not that it's a ton of overhead, but isn't your Config() function going to be run once for each line you're referencing it (5 times)? What are the pros and cons of running it once and saving the result as a struct, and then referencing that result struct instead of calling the function inline?

Posted by Adam Tuttle on Aug 17, 2007 at 12:10 PM


@Adam,

Very true. You could run something like:

<cfset Config() />

Before setting the Application.cfc settings and then just reference the $Config variable directly. I was only trying to not put any code before the settings.... it's just a personal thing. And yes, doing it this way would be more efficient than the way I demonstrated in the code.

Posted by Ben Nadel on Aug 17, 2007 at 12:19 PM


Post Comment  |  Ask Ben


Home   |   Web Log   |   ColdFusion   |   Projects   |   Resume   |   Job Form   |   Search   |   Contact
Epicenter Consulting - Custom Software Solutions for Business Evolution HostMySite.com - The Leader In ColdFusion Hosting