Ben Nadel
On User Experience (UX) Design, JavaScript, ColdFusion, Node.js, Life, and Love.
I am the chief technical officer at InVision App, Inc - a prototyping and collaboration platform for designers, built by designers. I also rock out in JavaScript and ColdFusion 24x7.
Meanwhile on Twitter
Loading latest tweet...
Ben Nadel at CFUNITED 2009 (Lansdowne, VA) with:

Ask Ben: Environment-Based Application.cfc Settings

By Ben Nadel on

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:

  • <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:

  • <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).




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() ?

Reply to this Comment

@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.

Reply to this Comment

@Ryan,

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

Reply to this Comment

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!

Reply to this Comment

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.

Reply to this Comment

@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?

Reply to this Comment

@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.

Reply to this Comment

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.

Reply to this Comment

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.

Reply to this Comment

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?

Reply to this Comment

@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.

Reply to this Comment

Ben,
we are regularly running into the situation of having to deploy application code on different servers with variety of configurations.
We found that moving the configuration outside the application as XML file worked best.
This allows us to copy all code and leave the config alone.
Within App.cfc the first line would be the call the init function, which then, loads and sets the config structure once.

Reply to this Comment

@Bilal,

I've worked with systems like that. They usually have something like a:

config.prod.xml
config.dev.xml

... or thereabouts. I think that works well. And, if you don't mind copying each XML file to each server, it's typically not a problem. The only thing that concerns me is when you have to take care to NOT override XML files. Meaning, if all of them were named:

config.xml

... but had different content on each server. In that case, it feels like something that would require too much thinking.

Reply to this Comment

Ben - Can you fill in a gap on my understanding of the Application.cfc lifecycle? My understanding is that a new instance of the App.cfc object is created with every web hit. So looking through your example above, how can the config structure ever actually be pulled from the cache since the 'this' scope is specific to a single instance of any object? Am I correct in believing that two hits to a site will create two App.cfc instances, each with their own this scope? Or is the this scope in App.cfc magic? Or maybe my mental model here is just off...

I would have thought that you'd want to cache something in a persistent scope like application (say, application.config) but you don't have access to that scope in App.cfc's pseudo-constructor right? (since CF doesn't know which application to bind the request to until the constructor finishes and it can look at the value of this.name)

Reply to this Comment

Ah, I misunderstood something you said above. When you say that "all subsequent method requests will simply return the cached setting structure," you mean *within the same HTTP request*. That cached config will still be rebuilt once for every request though.

Reply to this Comment

@Dave that is correct a reload of settings file is needed per request. The alternative is to use simple cache (i.e. ehCache/cachePut()/cacheGet() or similar) to stick the data in.
This will work outside application structure.
The trick is to create unique cache Keys so you no two apps on your box collide.
I use the directory as cacheKey:
strCacheKey = REReplace(strConfigDir,"[^0-9A-Za-z ]","","all")

Reply to this Comment

@Dave,

You are correct in you pointing out in the oddness of this approach. What I do these days is I just put the configuration stuff in the onApplicationStart() event handler and cache it in the Application scope. Or, as @Bilal points out, you can cache it using something like cachePut() / cacheGet() in ColdFusion 9.

I don't even bother using a separate CFC for it or a separate method. In my applications, the configuration tends to not be so big; I find that putting directly inline in the onApplicationStart() is perfect for 99% of what I do.

Reply to this Comment

For those looking at this from a performance point of view, it looks like config() will be called once per-request, because Application.cfc is recreated on each request and so $Config is cleared.
You can store the cache in the server scope to work around that, but you might want to use expandPath('/') some other per-application setting as the key.

Reply to this Comment

Post A Comment

You — Get Out Of My Dreams, Get Into My Comments
Live in the Now
Oops!
Comment Etiquette: Please do not post spam. Please keep the comments on-topic. Please do not post unrelated questions or large chunks of code. And, above all, please be nice to each other - we're trying to have a good conversation here.