Global Configuration Settings in Model-Glue:Unity
Some Suggestions
When making the transition from procedural CF to OO, sometimes what was SO simple before can become a challenge (at least the first time). For instance, the way we used to pass around our app's DSN value was probably something along the lines of setting an application variable in our application.cfm or cfc, thus making it accessible throughout our app while consolidating the value to a single location for ease of change later. But now that we've decided to force ourselves to learn to do things in an OO fashion, where on earth is the "correct" place, or what is the "correct" way to perform this same action?
In our procedural apps, common sense told us to "find a place within the execution path that always gets called, no matter what, and check for and set your global value there...". Let's use that same line of thinking for our OO app, too, and find an appropriate spot in which to set our global variable.
Even in an OO approach, CF still respects application.cfc/cfm, so setting the value there could be an option. However, one of the chief tenets of OO is encapsulation, meaning that we try to make each aspect of the application as non-dependent as possible on the rest of the application. Setting our DSN in application.cfc/cfm then means that likely our urge would be to stuff it into the application scope:
<cfset application.dsn = "Horton" />
If I want to use that value within any of my model CFCs then, SOMEWHERE I'd have to write a line of code that talks to the application scope; not a best practice, and indeed immediately makes my CFC dependent on the existence of some value outside of itself. No bueno.
How 'bout instead we utilize a configuration bean...a tiny object whose sole purpose in life is to pack around global values I might need here and there, and to make itself available anywhere within my app? Now that makes my common sense tingle with delight. Utilizing Model-Glue:Unity, let me share a couple of ways I have done this very thing.
At this juncture, if you aren't familiar with the member of the Model-Glue: Unity trinity called Coldspring, it's time you two met. Coldspring is an expert bean handler, and is quite a versatile fellow when it comes to creating beans and making them available wherever you'll be needing them. Coldspring does all of what it does based on the content of its XML configuration file (Coldspring.XML), so it is within that file where we will 'instruct' it regarding our configuration values.
At this point you're probably thinking to yourself, "okay self, all I need to do then is create a CFC that handles configuration values for my app. I'll make a getter and setter method for my DSN, one for my images directory path, one for....". Hey, that is definitely a good way of thinking. HOWEVER, Coldspring has a surprise for you, O Best Beloved. Coldspring can automagically create that configuration bean for you, and all you gotta do is tell it what you want it to contain via the Coldspring.XML file! Allow me to elucidate.
In order to auto-create one of these configuration beans, let's define it within Coldspring.XML, like so:
Now that we've defined it, from this moment onward we'll maintain our app settings RIGHT THERE. If the DSN changes, I'll change the value of the DSN entry key.
Making this configuration bean available throughout the app is a purposeful endeavor, meaning that you have to explicity tell Coldspring to make it available to a specific CFC. We do this by "injecting" it into other CFCs that we've told Coldspring about. Looky here:
I've told Coldspring "I have an EmailService.CFC, and it is expecting an argument value named 'AppConfiguration', so when you instantiate EmailService for me, please go ahead and pass in an instance of my AppConfiguration bean as that value. Thanks man."
Okay, the only other thing you have to do in order for this to work smoothly is to ensure that the object you're wanting to inject the configuration bean into is prepared to receive it. With the approach we're taking, the proper way to do this is to make sure your CFC has an init method, and within that init method create a required argument named exactly the same as your constructor-arg name. Like so:
Okay, so now we have our Coldspring simple config object injected. Pay attention, this is important: for every value we defined for this config object, you will access it via the following method: getConfigSetting([value name]). For instance, if within our EmailService object we have a method that needs the DSN value, we'll grab it with
<cfset myDSN = variables._config.getConfigSetting("DSN") />
Alternatively (and this is what I did), you can create a generic "GetConfigSetting" method within your recipient object, like so:
With that method present in your recipient object, you would access the DSN like this:
<cfset myDSN = GetConfigSetting("DSN") />
It's a little cleaner, I suppose.
Now, you could be thinking that since your config object contains values that may also be needed within a view, such as a CSS path, or an images directory path, that you'll need a way to make it available there, too. Let me toss out a suggestion based on the way I have done it.
Everytime a MG:U event is called, you can have listeners that execute beforehand based on the fact that they're listening for a call to "OnRequestStart", as in this snippet from my modelglue.XML file:
<controller name="MyController" type="controller.Controller">
<message-listener message="OnRequestStart" function="loadConfiguration" />
</controller>
You can, within the method being referenced in onRequestStart, stuff the configuration bean into the event object, thus making it available to every other object referencing that event, INCLUDING your views! Let's look at the controller object mentioned in this example:
<cfcomponent displayname="Controller" extends="ModelGlue.unity.controller.Controller" output="false">
<!--- Autowire / private getter --->
<cffunction name="setAppConfiguration" access="public" returntype="void" output="false">
<cfargument name="AppConfiguration" required="true" type="any" />
<cfset variables._config = arguments.AppConfiguration />
</cffunction>
<cffunction name="getAppConfiguration" access="private" returntype="any" output="false">
<cfreturn variables._config />
</cffunction>
<!--- Place the configuration bean into the viewstate --->
<cffunction name="loadConfiguration" access="public" returnType="void" output="false">
<cfargument name="event" type="any">
<cfset arguments.event.setValue("appConfiguration", getAppConfiguration()) />
</cffunction>
</cfcomponent>
A few things to point out here:
1. Because this object is a controller object, Coldspring gives us the privilege of autowiring in beans that it knows about by simply providing it with a "get" and "set" method named according to the bean we want to inject. In this instance, Coldspring already had created for us a bean called AppConfiguration, so by virtue of the fact that Coldspring saw our controller object had a method named "getAppConfiguration" and "setAppConfiguration", it assumed we wanted it to inject an instance of the AppConfiguration bean. Coool.
2. On request start, Model-Glue is calling our "loadConfiguration" method, which is taking the injected configuration bean and placing it into the Event bucket (some like to call it the event 'bus', but thinking of it as a bucket makes more sense to me). This automatically means that the configuration bean will be available in our viewstate as well, and from within any view can be referenced like
<cfset appConfig = viewstate.getValue("appConfiguration") />
<link rel="stylesheet" type="text/css" href="<cfoutput>#appConfig.getConfigSetting("CSSPath")#</cfoutput>styles.css" media="screen" />
Now, having shared all of that, I will tell you that if you're using Reactor within your app, you were already defining the DSN within the Coldspring Reactor Bean, and may not want to define the DSN twice, but rather would like to use Reactor's DSN value throughout your app. This can be done just as easily, only you'll be injecting not your appConfiguration bean into the CFCs that need the DSN value, but the "reactorConfiguration" bean instead (already exists within your Coldspring.xml file). Once injected, you'll get at your DSN value with a method called "getDSN()".
Here's something else to chew on that may be helpful to you (as it was for me): Besides just a global application config bean, you can also define configuration beans for different services that your app has. For instance, my app performs image manipulation, so within my Coldspring.xml file I defined a configuration bean called "ImageServiceConfig" that contains settings for thumbnail dimensions, large image dimensions, naming conventions, pathing information, etc., then I inject that bean into my ImageService.cfc. I do the same thing for my EmailService.cfc. This allows me to truly consolidate all of my application settings within my Coldspring.xml, simplifying maintenance in that regard.
That, Boys and Girls, is injection at its finest, and it allows me to maintain my app's global values in one place, just like I used to do with application.cfc/cfm.
Doug out.
In our procedural apps, common sense told us to "find a place within the execution path that always gets called, no matter what, and check for and set your global value there...". Let's use that same line of thinking for our OO app, too, and find an appropriate spot in which to set our global variable.
Even in an OO approach, CF still respects application.cfc/cfm, so setting the value there could be an option. However, one of the chief tenets of OO is encapsulation, meaning that we try to make each aspect of the application as non-dependent as possible on the rest of the application. Setting our DSN in application.cfc/cfm then means that likely our urge would be to stuff it into the application scope:
<cfset application.dsn = "Horton" />
If I want to use that value within any of my model CFCs then, SOMEWHERE I'd have to write a line of code that talks to the application scope; not a best practice, and indeed immediately makes my CFC dependent on the existence of some value outside of itself. No bueno.
How 'bout instead we utilize a configuration bean...a tiny object whose sole purpose in life is to pack around global values I might need here and there, and to make itself available anywhere within my app? Now that makes my common sense tingle with delight. Utilizing Model-Glue:Unity, let me share a couple of ways I have done this very thing.
At this juncture, if you aren't familiar with the member of the Model-Glue: Unity trinity called Coldspring, it's time you two met. Coldspring is an expert bean handler, and is quite a versatile fellow when it comes to creating beans and making them available wherever you'll be needing them. Coldspring does all of what it does based on the content of its XML configuration file (Coldspring.XML), so it is within that file where we will 'instruct' it regarding our configuration values.
At this point you're probably thinking to yourself, "okay self, all I need to do then is create a CFC that handles configuration values for my app. I'll make a getter and setter method for my DSN, one for my images directory path, one for....". Hey, that is definitely a good way of thinking. HOWEVER, Coldspring has a surprise for you, O Best Beloved. Coldspring can automagically create that configuration bean for you, and all you gotta do is tell it what you want it to contain via the Coldspring.XML file! Allow me to elucidate.
In order to auto-create one of these configuration beans, let's define it within Coldspring.XML, like so:
<bean id="AppConfiguration" class="ModelGlue.Bean.CommonBeans.SimpleConfig">
<property name="Config">
<map>
<!-- this is the DSN used throughout the app... -->
<entry key="DSN"><value>Horton</value></entry>
<!-- path the CSS directory -->
<entry key="CSSPath"><value>/appCSS/</value></entry>
</map>
</property>
</bean>
(Note: Notice the class of our configuration bean...we're leveraging a built-in class of model-glue)<property name="Config">
<map>
<!-- this is the DSN used throughout the app... -->
<entry key="DSN"><value>Horton</value></entry>
<!-- path the CSS directory -->
<entry key="CSSPath"><value>/appCSS/</value></entry>
</map>
</property>
</bean>
Now that we've defined it, from this moment onward we'll maintain our app settings RIGHT THERE. If the DSN changes, I'll change the value of the DSN entry key.
Making this configuration bean available throughout the app is a purposeful endeavor, meaning that you have to explicity tell Coldspring to make it available to a specific CFC. We do this by "injecting" it into other CFCs that we've told Coldspring about. Looky here:
<bean id="EmailService" class="model.EmailService">
<constructor-arg name="AppConfiguration">
<ref bean="AppConfiguration" />
</constructor-arg>
</bean>
<constructor-arg name="AppConfiguration">
<ref bean="AppConfiguration" />
</constructor-arg>
</bean>
I've told Coldspring "I have an EmailService.CFC, and it is expecting an argument value named 'AppConfiguration', so when you instantiate EmailService for me, please go ahead and pass in an instance of my AppConfiguration bean as that value. Thanks man."
Okay, the only other thing you have to do in order for this to work smoothly is to ensure that the object you're wanting to inject the configuration bean into is prepared to receive it. With the approach we're taking, the proper way to do this is to make sure your CFC has an init method, and within that init method create a required argument named exactly the same as your constructor-arg name. Like so:
<cffunction name="init" returntype="EmailService" output="false" hint="Constructor">
<cfargument name="AppConfiguration" required="true" />
<cfset variables._config = arguments.AppConfiguration />
<cfreturn this />
</cffunction>
<cfargument name="AppConfiguration" required="true" />
<cfset variables._config = arguments.AppConfiguration />
<cfreturn this />
</cffunction>
Okay, so now we have our Coldspring simple config object injected. Pay attention, this is important: for every value we defined for this config object, you will access it via the following method: getConfigSetting([value name]). For instance, if within our EmailService object we have a method that needs the DSN value, we'll grab it with
<cfset myDSN = variables._config.getConfigSetting("DSN") />
Alternatively (and this is what I did), you can create a generic "GetConfigSetting" method within your recipient object, like so:
<cffunction name="GetConfigSetting" access="private" returntype="string" output="false">
<cfargument name="name" required="true" type="string" />
<cfreturn variables._config.getConfigSetting(arguments.name) />
</cffunction>
<cfargument name="name" required="true" type="string" />
<cfreturn variables._config.getConfigSetting(arguments.name) />
</cffunction>
With that method present in your recipient object, you would access the DSN like this:
<cfset myDSN = GetConfigSetting("DSN") />
It's a little cleaner, I suppose.
Now, you could be thinking that since your config object contains values that may also be needed within a view, such as a CSS path, or an images directory path, that you'll need a way to make it available there, too. Let me toss out a suggestion based on the way I have done it.
Everytime a MG:U event is called, you can have listeners that execute beforehand based on the fact that they're listening for a call to "OnRequestStart", as in this snippet from my modelglue.XML file:
<controller name="MyController" type="controller.Controller">
<message-listener message="OnRequestStart" function="loadConfiguration" />
</controller>
You can, within the method being referenced in onRequestStart, stuff the configuration bean into the event object, thus making it available to every other object referencing that event, INCLUDING your views! Let's look at the controller object mentioned in this example:
<cfcomponent displayname="Controller" extends="ModelGlue.unity.controller.Controller" output="false">
<!--- Autowire / private getter --->
<cffunction name="setAppConfiguration" access="public" returntype="void" output="false">
<cfargument name="AppConfiguration" required="true" type="any" />
<cfset variables._config = arguments.AppConfiguration />
</cffunction>
<cffunction name="getAppConfiguration" access="private" returntype="any" output="false">
<cfreturn variables._config />
</cffunction>
<!--- Place the configuration bean into the viewstate --->
<cffunction name="loadConfiguration" access="public" returnType="void" output="false">
<cfargument name="event" type="any">
<cfset arguments.event.setValue("appConfiguration", getAppConfiguration()) />
</cffunction>
</cfcomponent>
A few things to point out here:
1. Because this object is a controller object, Coldspring gives us the privilege of autowiring in beans that it knows about by simply providing it with a "get" and "set" method named according to the bean we want to inject. In this instance, Coldspring already had created for us a bean called AppConfiguration, so by virtue of the fact that Coldspring saw our controller object had a method named "getAppConfiguration" and "setAppConfiguration", it assumed we wanted it to inject an instance of the AppConfiguration bean. Coool.
2. On request start, Model-Glue is calling our "loadConfiguration" method, which is taking the injected configuration bean and placing it into the Event bucket (some like to call it the event 'bus', but thinking of it as a bucket makes more sense to me). This automatically means that the configuration bean will be available in our viewstate as well, and from within any view can be referenced like
<cfset appConfig = viewstate.getValue("appConfiguration") />
<link rel="stylesheet" type="text/css" href="<cfoutput>#appConfig.getConfigSetting("CSSPath")#</cfoutput>styles.css" media="screen" />
Now, having shared all of that, I will tell you that if you're using Reactor within your app, you were already defining the DSN within the Coldspring Reactor Bean, and may not want to define the DSN twice, but rather would like to use Reactor's DSN value throughout your app. This can be done just as easily, only you'll be injecting not your appConfiguration bean into the CFCs that need the DSN value, but the "reactorConfiguration" bean instead (already exists within your Coldspring.xml file). Once injected, you'll get at your DSN value with a method called "getDSN()".
Here's something else to chew on that may be helpful to you (as it was for me): Besides just a global application config bean, you can also define configuration beans for different services that your app has. For instance, my app performs image manipulation, so within my Coldspring.xml file I defined a configuration bean called "ImageServiceConfig" that contains settings for thumbnail dimensions, large image dimensions, naming conventions, pathing information, etc., then I inject that bean into my ImageService.cfc. I do the same thing for my EmailService.cfc. This allows me to truly consolidate all of my application settings within my Coldspring.xml, simplifying maintenance in that regard.
That, Boys and Girls, is injection at its finest, and it allows me to maintain my app's global values in one place, just like I used to do with application.cfc/cfm.
Doug out.

