Managing Environment-Specific Application Settings Via A ColdSpring Bean
When I develop my ColdFusion applications, I work in two different environments. I develop the applications on my local machine, then deploy them to the server when I reach particular milestones or when it's ready for limited user testing. Even though I mimic the server environment as best as I can on my local machine, there are still certain aspects of the application that are different when it executes on the server. For example, my local machine isn't configured to send and receive e-mail, but I want my application (when it's running in production) to send out an e-mail when certain functions are executed.
What I used to do to solve this problem was to run a series of functions via the onApplicationStart() function in Application.cfc that would determine where the application was running (development or production), record that fact in an application-scoped variable called "locale", and then set a series of additional application-scoped variables based on what the current environment was. I would then code certain aspects of my applications, such as those responsible for sending e-mail, to examine the value of the application.locale variable to determine if certain function should execute and then use any other application-level settings necessary for the actual execution (like the e-mail address associated with the application).
I recently started using Model-Glue as my application framework, and since Model-Glue uses ColdSpring to manage some of its internal settings, I decided to revise my technique to take advantage of ColdSpring as well.
Here is the code of the appConfig.cfc I created for managing the application settings:
<cfcomponent displayname="appConfig" hint="I provide the configuration settings for the application dependent on where the application is running."> <cffunction name="init" access="public" returntype="any" hint="I accept all of the possible setting configurations"> <cfargument name="universalSettings" type="struct" required="true" hint="A struct of all of the configuration settings that are true regardless of environment" /> <cfargument name="localSettings" type="struct" required="true" hint="A struct of all of the configuration settings for the local environment" /> <cfargument name="remoteSettings" type="struct" required="true" hint="A struct of all of the configuration settings for the remote environment" /> <cfset var loc= StructNew()> <cfset variables.config= StructNew()> <cfloop collection="#arguments.universalSettings#" item="loc.ukey"> <cfset variables.config[loc.ukey]= arguments.universalSettings[loc.ukey]> </cfloop> <cfset loc.env= determineEnvironment()> <cfloop collection="#arguments[loc.env]#" item="loc.ekey"> <cfset variables.config[loc.ekey]= arguments[loc.env][loc.ekey]> </cfloop> <cfreturn this /> </cffunction> <cffunction name="determineEnvironment" access="private" output="false" returntype="string" hint="I determine the application environment"> <cfif Left(cgi.cf_template_path,6) EQ "/Users"> <cfreturn "localSettings" /> <cfelse> <cfreturn "remoteSettings" /> </cfif> </cffunction> <cffunction name="getConfig" access="public" output="false" returntype="any" hint="I return a config setting"> <cfargument name="configName" type="string" required="true" hint="The name of the configuration setting"> <cfreturn variables.config[arguments.configName]> </cffunction> <cffunction name="setConfig" access="public" output="false" returntype="void" hint="I set a config setting"> <cfargument name="configName" type="string" required="true" hint="The name of the configuration setting"> <cfargument name="configValue" type="any" required="true" hint="The value/data to store in the configuration setting"> <cfset variables.config[arguments.configName]= arguments.configValue> </cffunction> </cfcomponent>
The init() constructor function takes three structs as arguments: one that contains the universal application settings that are the same regardless of environment, one that contains the application configuration settings for my local/development environment, and one that contains the configuration settings specific to my remote/production environment. The universal settings are copied into the "config" struct in the variables scope, and then a call is made out to the determineEnvironment() function to determine the current application environment. The determineEnvironment() function uses the cgi.cf_template_path to determine if the current ColdFusion template is running on a Macintosh system (my work laptop is a Mac) and returns the name of the constructor argument that should be used to populate the rest of the "config" struct. The getConfig() function provides a means of retrieving the value of a configuration setting, while the setConfig() function allows you to change a configuration setting.
So populating this appConfig object with the correct application configuration settings is simply a matter of providing the three different configuration structs into the init() constructor argument. That's where ColdSpring comes into play: ColdSpring is ideal for managing and documenting the data and objects that need to be injected into singleton objects. Here is an example ColdSpring bean definition (within a ColdSpring.xml file) for the appConfig object:
<bean id="appConfig" class="myApp.model.csBeans.appConfig"> <constructor-arg name="universalSettings"> <map> <entry key="ds"><value>myDatasource</value></entry> <entry key="appName"><value>myApp</value></entry> <entry key="approot"><value>/myApp</value></entry> </map> </constructor-arg> <constructor-arg name="localSettings"> <map> <entry key="locale"><value>local</value></entry> <entry key="emailNotification"><value>false</value></entry> <entry key="uploadDirectory"><value>/Users/Brian/Websites/myApp/fileRepo/</value></entry> </map> </constructor-arg> <constructor-arg name="remoteSettings"> <map> <entry key="locale"><value>remote</value></entry> <entry key="emailNotification"><value>true</value></entry> <entry key="errorSenderName"><value>myApp System</value></entry> <entry key="errorSenderEmail"><value>myApp@domain.org</value></entry> <entry key="errorDefaultSubject"><value>An error has occurred in the myApp system</value></entry> <entry key="uploadDirectory"><value>/wwwroot/myApp/uploadedFiles/</value></entry> </map> </constructor-arg> </bean>
As you can see, the ColdSpring bean definition contains three <constructor-arg> XML blocks, one for each argument to be passed into the appConfig.cfc during instantiation, and each constructor argument contains a <map> XML block that defines the struct variable for that argument.
What's the advantage of defining these settings in ColdSpring? Once the appConfig object is defined as a ColdSpring bean, I can inject an appConfig bean into any other singleton bean object I define in ColdSpring or retrieve appConfig from the ColdSpring beanFactory object (which is usually instantiated during application startup). Plus, Model-Glue makes it very easy to inject ColdSpring beans into any of your controller objects simply by passing a list of ColdSpring bean names into a "beans" attribute in either the controller's <cfcontroller> tag or the starting <controller> tag of the controller's XML block in the ModelGlue.xml file of the application (see the "How to Use Bean Injection" page on Model-Glue documentation site for more details on that). And it makes sense to put my application configuration settings alongside the Model-Glue configuration settings in ColdSpring so there's only one file I need to refer to for my configuration data.
The end result? I end up with an appConfig object containing the correct application settings for the current environment that my code can retrieve variable settings from whenever a function's behavior is dependent upon the application environment.
This technique I've just outlined works for me because I'm a solo developer with only two application environments, a simple way of determining which environment an application is running in, and usually only a small number of environment-specific app settings to deal with. If you're working in a team or with multiple application environments, you made need a more robust approach. One option you might want to consider is the Environment Config project on RIAForge.org. The project also lets you leverage ColdSpring to define environment-specific variables, but it provides more options for distinguishing between different application environments and more flexibility in how you structure and retrieve your application configuration information. It's still in beta at the moment, but it looks promising.