Create Simple Configuraiton Beans using JSON
2007 October 25
The more I use JSON, the more enamoured I am with the simplicity of the format. For configuration settings, JSON can easily be decoded into structures used to populate simple configuration beans. While updating a legacy application to a service oriented, framework driven implementation, I found the need to access default settings in different contexts. In the legacy app, these defaults were set in many list templates to both pre-populate a form and filter a query. My first inclination was to use Mach-II properties to store the config settings, but with many different pages all having some default params, that would become unwieldy. In order to provide access to the default values in both a view and a Mach-II listener, I decided to build a "ConfigurationService" that would serve up event-focused simple configuration beans from a JSON configuration file.The ConfigurationService maintains a global configuration list of default parameters required for specific events. I decided, in the short term at least, to map the default configuration params to the event under which they are required. For example, to display a user list my event name is 'listUsers'. The global configuration will have a key named 'listUsers' that contains the default parameters required.
For the sake of flexibility, I thought it would be interesting to be able to use various formats (JSON, XML, YAML, or any other data definition language) and provide config translators for each. With that in mind, I had a go at the Strategy pattern to encapsulate the variations for translating a raw configuration file. Granted, this is the first time I've used the Strategy pattern, so I might be way off base.
Here's a UML diagram for the classes involved.
Note that I'm using cfjson for de-serialization, thus the dependency in the JsonConfigTranslator. CF8 provides native support for JSON. :)
And here's the ColdSpring XML to create the participants.
<bean id="json" class="model.util.json" />
<bean id="JsonConfigTranslator" class="model.JsonConfigTranslator">
<constructor-arg name="json">
<ref bean="json" />
</constructor-arg>
</bean>
<bean id="SimpleConfigFactory" class="model.SimpleConfigFactory" />
<bean id="ConfigurationService" class="model.service.ConfigurationService">
<constructor-arg name="factory">
<ref bean="SimpleConfigFactory" />
</constructor-arg>
<constructor-arg name="translator">
<ref bean="JsonConfigTranslator" />
</constructor-arg>
<constructor-arg name="jsonConfig">
<value>/machadmin/config/config.json</value>
</constructor-arg>
</bean>
Here's an example JSON configuration file.
{ 'listUsers': { 'FirstName': '', 'LastName': '', 'Email': '', 'StartDate': '2007/01/01', 'EndDate': '', 'OrderBy': 'DateCreated DESC', 'MaxRecords': 400, 'MaxOverride': 0, 'MaxRows': 40, 'StartRow': 1 } }With the components, bean configuration and JSON configuration all set, I was ready to test the defaults in the view page and happy to replace a lot of logic with something more cohesive. Let's start with the Mach-II xml for the event using an example of a list page for users.
<event-handler event="listUsers" access="public">
<notify listener="userListener" method="getDefaultConfig" />
<view-page name="user_list" contentArg="content" />
<announce event="page_layout" copyEventArgs="true" />
</event-handler>
And the getDefaultConfig method in userListener.
<cffunction name="getDefaultConfig" access="public" output="false" returntype="void">
<cfargument name="event" type="MachII.framework.Event" required="true" />
<cfset arguments.event.setArg("defaultConfig",variables.configService.getConfigBean(arguments.event.getRequestName()))>
</cffunction>
Finally, in the view page, I use the following snippet to store a reference to the default params in variables scope.
<cfset defaultConfig = event.getArg("defaultConfig") />
<cfset defaultParams = defaultConfig.getParamList() />
<cfloop index="i" from="1" to="#ListLen(defaultParams)#">
<cfset paramName = ListGetAt(defaultParams,i) />
<cfset variables[paramName] = event.getArg(paramName,defaultConfig.getParam(paramName)) />
</cfloop>
What I really like about how the snippet above works, is that when an actual default param is not available as an event arg, it will be replaced with the default config value. That's a really convenient option for the getArg() method. What I don't like about the snippet above, is that I will have to reuse it in my user listener to build a structure to pass to my service (when I get around to moving that out of the view).
In summary, this setup is not as "Simple" as the title implies, but it does provide a solution to moving defaults settings out of the view. And although I'm using it specifically as a means of moving default params out of views, it could be use d for other more common settings like datasource settings, mailserver settings, etc.
If this looks like a fit for a use case you've encountered, I'll be happy to package the components and make them available.