Dynamic Configuration with Environment Config And a Coldspring Config Factory

Ideas , Configuration , ColdSpring Add comments
When developing web application, it is very likely that at some point you will need to create dynamic configuration for your application in terms of development, staging and production environments. I recently discovered the excellent Environment Config project by Rolando Lopez. Rolando has created a very interesting package with lots of robust features for configuring your application dynamically. There are portions of the package that I must admit, I'm not using (yet). What follows is my current setup for integrating the environment config with a custom factory.

Getting Started

To use the environment config, you create an xml file in this format. <?xml version="1.0" encoding="UTF-8"?>
<environments>
<default>
<config>
<!-- main vars -->
<property name="adminEmail">admin@domain.com</property>
<property name="transferConfigPath">/config/transfer/Transfer.xml</property>
<property name="definitionsPath">/config/definitions</property>
<property name="exampleMap">
<map>
<entry key="key1"><value>value1</value></entry>
<entry key="key2"><value>value2</value></entry>
</map>
</property>
</config>
</default>

<!-- local -->
<environment id="local">
<patterns>
<pattern>^project.fancybread.local</pattern>
</patterns>
<config>
<property name="mailServer">localhost</property>
<property name="datasourcePath">/config/transfer/local_ds.xml</property>
</config>
</environment>

<!-- staging -->
<environment id="staging">
<patterns>
<pattern>^project.fancybread.local</pattern>
</patterns>
<config>
<property name="mailServer">staging.mailserver</property>
<property name="datasourcePath">/config/transfer/staging_ds.xml</property>
</config>
</environment>

<!-- production -->
<environment id="production">
<patterns>
<pattern>^www.somedomain.com</pattern>
</patterns>
<config>
<property name="mailServer">production.mailserver</property>
<property name="datasourcePath">/config/transfer/production_ds.xml</property>
</config>
</environment>
</environments>
I've trimmed down the settings for this example. The important thing to note is that the Environment Config will return a struct of the properties you define in the default config and the specific environment config properties by calling either getEnvironmentById() or getEnvironmentByUrl() passing either the environment id or the host name that is matched by regular expression to the pattern of you environment. I like to use the host name and getEnvironmentByUrl().

Coldspring Integration

To configure Environment for use in Coldspring, I set a bean definition for it and a custom config factory named (most unimaginatively) ConfigFactory. Here are the Coldspring bean definitions. <bean id="environmentConfig" class="model.Environment">
<constructor-arg name="xmlFile">
<value>${configFile}</value>
</constructor-arg>
</bean>

<bean id="configFactory" class="model.ConfigFactory">
<constructor-arg name="hostName">
<value>${hostName}</value>
</constructor-arg>
<constructor-arg name="environmentConfig">
<ref bean="environmentConfig" />
</constructor-arg>
</bean>
Here's the Application.cfc code for setting up Coldspring. Rather than pass a slough of variables to Coldspring, I'm passing only the location for the environment config xml and the CGI.HTTP_HOST. <cfset var beanDefFileLocation = expandPath('/config/beans.xml.cfm')>
<cfset var params = structNew() />
<cfset params['hostName'] = CGI.HTTP_HOST>
<cfset params['configFile'] = '/config/environment.xml.cfm'>
<cfset application.beanFactory = createObject("component","coldspring.beans.DefaultXmlBeanFactory").init(StructNew(),params) />
<cfset application.beanFactory.loadBeans(beanDefinitionFileName=beanDefFileLocation, constructNonLazyBeans=false) />

Coldspring Factory Beans At Work

When I started learning Coldspring, I had no idea how to use it beyond the most basic configurations. One item that puzzled me were factory beans. I incorrectly assumed that a factory bean required some special magic and a bean declaration in the condspring xml had to bean object. It wasn't until recently that I learned that this is not set in stone. A "bean" declaration in Coldspring can be any value and a factory bean and factory method can be defined for any object. Armed with that knowledge, I set out to wrap the environment config in a simple "factory" to easily swap values based on the HTTP_HOST. Here's the code for setting the Transfer config using the ConfigFactory. <bean id="transferFactory" class="transfer.TransferFactory">
<constructor-arg name="datasourcePath"><ref bean="datasourcePath" /></constructor-arg>
<constructor-arg name="configPath"><ref bean="transferConfigPath" /></constructor-arg>
<constructor-arg name="definitionPath"><ref bean="definitionsPath" /></constructor-arg>
</bean>
<bean id="datasource" factory-bean="transferFactory" factory-method="getDatasource" />
<bean id="transfer" factory-bean="transferFactory" factory-method="getTransfer" />
<bean id="transferConfigPath" factory-bean="configFactory" factory-method="getSetting">
<constructor-arg name="key">
<value>transferConfigPath</value>
</constructor-arg>
</bean>
<bean id="datasourcePath" factory-bean="configFactory" factory-method="getSetting">
<constructor-arg name="key">
<value>datasourcePath</value>
</constructor-arg>
</bean>
<bean id="definitionsPath" factory-bean="configFactory" factory-method="getSetting">
<constructor-arg name="key">
<value>definitionsPath</value>
</constructor-arg>
</bean>
While passing arguments to the getSetting() as a factory method works, I found this to be rather verbose, so I used it as an opportunity to incorporate onMissingMethod in an cfc for the first time.

Simplifying the Config with OnMissingMethod

OnMissingMethod is a very powerful feature of Coldfusion 8. I've seen some pretty fantastic examples of software that leverage onMissingMethod (oMM). By using a very simple convention for my oMM in my ConfigFactory, I'm able to reduce all the definitions above that use getSetting() to the following. <bean id="transferConfigPath" factory-bean="configFactory" factory-method="getTransferConfigPath" />
<bean id="datasourcePath" factory-bean="configFactory" factory-method="getDatasourcePath" />
<bean id="definitionsPath" factory-bean="configFactory" factory-method="getDefinitionsPath" />
Here's the full code for the ConfigFactory. <cfcomponent displayname="ConfigFactory" output="false">

<cfset variables.settings = structNew()>

<cffunction name="init" access="public" output="false" returntype="any">
<cfargument name="hostName" type="string" required="true">
<cfargument name="environmentConfig" type="model.Environment" required="true">
<cfset variables.settings = arguments.environmentConfig.getEnvironmentByUrl(arguments.hostName)>
<cfreturn this>
</cffunction>

<cffunction name="getAllSettings" access="public" output="false" returntype="struct">
<cfreturn variables.settings>
</cffunction>

<cffunction name="getSetting" access="public" output="false" returntype="any">
<cfargument name="key" type="string" required="true">
<cftry>
<cfreturn variables.settings[arguments.key]>
<cfcatch type="any">
<cfthrow type="custom" message="Setting does not exist.">
</cfcatch>
</cftry>
</cffunction>

<cffunction name="onMissingMethod" access="public" output="false" returntype="any">
<cfargument name="MissingMethodName" type="string" required="true" />
<cfargument name="MissingMethodArguments" type="struct" required="true" />
<cfif (Left(arguments.MissingMethodName,3) eq "get")>
<cfreturn getSetting(Right(arguments.MissingMethodName,Len(arguments.MissingMethodName)-3))>
</cfif>
</cffunction>

</cfcomponent>
Using onMissingMethod in this way makes it a breeze to add properties to my environment configuration and later reference them using the ConfigFactory and a correctly named factory-method. Granted, there are many different ways to dynamically create configuration settings, using ant for instance. I'm not yet an ant expert. Until then, I like the combination of the environment config and the dynamic config factory.

3 responses to “Dynamic Configuration with Environment Config And a Coldspring Config Factory”

  1. Brian Kotek Says:
    Paul, unless I'm misreading what you're doing, I think you can accomplish the same thing in a much easier way. Instead of the custom &quot;factory bean&quot; calls to get each and every dynamic property, you can just pass the environment properties structure into ColdSpring when you create the Bean Factory:

    &lt;cfset environment = CreateObject( 'component','components.utility.Environment' ).init(xmlFile='/config/environment.xml')
    &lt;cfset globalProperties = environment.getEnvironmentByID('dev') /&gt;      
    &lt;cfset application.beanFactory = CreateObject('component', 'coldspring.beans.DefaultXmlBeanFactory').init() /&gt;
    &lt;cfset application.beanFactory.loadBeans(globalProperties.coldSpringConfig, globalProperties) /&gt;

    In that case, you're just passing in the struct containing your properties to ColdSpring, and it will replace any of your dynamic properties where you're using the dynamic property syntax ${myProp}. Make sense, or am I misunderstanding what you're trying to do?

    See you at CFUnited?
  2. Paul Marcotte Says:
    Hi Brian,

    I had intended on passing the ConfigFactory to any services that required access to specific settings, but I see where your suggestion is much more simple for passing the dynamic parameters rather than writing bean references for them.

    I guess it's back to the drawing board for me! ;-)
  3. UGG Says:
    <p><a href="http://www.uggbootsretails.com"; target="_blank">UGG Argyle Knit</a> features genuine twin-face sheepskin for refreshing comfort. It's one of our tallest boots and looks great all the way up or cuffed down. <a href="http://www.uggbootsretails.com"; target="_blank">UGG boots</a> in our Classic Collection feature a soft foam insole covered with genuine sheepskin and have a molded EVA light such as <a href="http://www.uggbootsretails.com"; target="_blank">UGG Sundance</a> and <a href="http://www.uggbootsretails.com"; target="_blank">UGG Bailey Button</a>, the flexible outsole designed for amazing comfort with every step. Beat the chill in superior style with the <a href="http://www.uggbootsretails.com"; target="_blank">UGG Ultra Tall</a>. The <a href="http://www.uggbootsretails.com"; target="_blank">Bailey Button 1873</a> of light and flexible outsole along with a suede heel guard provides durable wear all season long. Detailed with three oversized wood buttons, this style can be styled up and buttoned, slouched and slightly unbuttoned, or completely cuffed. <a href="http://www.uggbootsretails.com"; target="_blank">UGG Ultra Short</a> is also a good choice for people to refuge from the cold.Just buy one of the ugg boots for your family and yourself, it’s really a good gift both for you famlily and you.Action is better than thinking in heart.</p>

Leave a Reply



Powered by Mango Blog. Design and Icons by N.Design Studio