Based on advice from Sean Corfield, I set up my application as a series of three sub-applications. A public application, a staff application and a superuser application. This is a great way to divide and conquer an application separating controllers and configuration but there aren’t many concrete examples of how to go about this leaving some people confused.
One possible (and popular) configuration for sub-applications is to leverage a single instance of Coldspring so that all of your apps are sharing the same service layer. However, with no complete XML files posted, it’s easy to misconfigure Model-Glue leading to strange behavior.
In my staff application, I have a feature that lets staff toggle the visibility of an event on our public calendar. What should happen is changing the visibility status will rebuild our calendar cache and the public calendar will reflect the change on the next page load. What was happening was: nothing. Changing the status as a staffer was calling the event but not updating the public calendar cache. Upon further inspection I found that there were two instances of my service layer; one for each sub-application! The cache was being updated, but only by the instances of my CalendarService used by the staff application. The public app was never aware of any changes.
After much rooting around, I saw some brief references to “separating out your global Coldspring beans” when using the PARENT_BEAN_FACTORY feature of Model-Glue. I decided to explore this a bit and stumbled upon a fix for my situation. To help others prevent repeating my mistake, I will now share a top-to-bottom sub-application set up that uses ColdSpring and Transfer.
We’re going to work with one primary and one sub-application using the following directory layout. I’ll show a simple but complete example of each file listed below:
/Application.cfc
/index.cfm
/config/coldspring.xml
/config/beans.xml
/config/modelglue.xml
/staff/index.cfm
/staff/config/coldspring.xml
/staff/config/modelglue.xml
Many Sub-Applications, One Service Layer
First, you must create a single instance of a bean factory by instantiating Coldspring into the application scope in your onApplicationStart. My global beans are defined in a file /config/beans.xml. This is from my onApplicationStart in /Application.cfc:
<cfset application.cs = createObject("component", "coldspring.beans.DefaultXmlBeanFactory").init() />
<cfset application.cs.loadBeansFromXmlFile(expandPath("config/beans.xml"), true) />
The beans.xml defines my service layer and Transfer instance. These are the pieces of my model that I want to share among all of my Model-Glue applications. This can also include other coldspring files or additional configuration files:
<beans>
<!-- security configuration for AuthenticationService -->
<import resource="/ModelGlue/actionpacks/security/config/coldspring.xml" />
<!-- config data -->
<import resource="/config/bounce_signatures.xml" />
<import resource="/config/globalconfig.xml" />
<bean id="calendarGateway" class="model.calendar.calendarGateway">
<constructor-arg name="transfer"><bean id="transfer" factory-bean="ormService" factory-method="getTransfer" /></constructor-arg>
</bean>
<bean id="calendarService" class="model.calendar.calendarService">
<constructor-arg name="transfer"><bean id="transfer" factory-bean="ormService" factory-method="getTransfer" /></constructor-arg>
<constructor-arg name="zipcodeService"><ref bean="zipcodeService" /></constructor-arg>
<constructor-arg name="calendarGateway"><ref bean="calendarGateway" /></constructor-arg>
<constructor-arg name="feedService"><ref bean="feedService" /></constructor-arg>
</bean>
<!-- create an instance of Transfer -->
<alias alias="ormService" name="ormService.Transfer" />
<bean id="ormService.Transfer" class="transfer.TransferFactory">
<constructor-arg name="configuration"><ref bean="transferConfiguration" /></constructor-arg>
</bean>
<!-- datasource and ORM adapter -->
<bean id="datasource" factory-bean="ormService" factory-method="getDatasource" singleton="true" />
<!-- This is your application specific Transfer Configuration. Paths are from the webroot or mapping -->
<bean id="transferConfiguration" class="transfer.com.config.Configuration">
<constructor-arg name="datasourcePath"><value>/config/transfer/datasource.xml</value></constructor-arg>
<constructor-arg name="configPath"><value>/config/transfer/transfer.xml</value></constructor-arg>
<constructor-arg name="definitionPath"><value>/generated</value></constructor-arg>
</bean>
</beans>
My beans.xml file is more complicated but the point is that you define your service layer and beans in the beans.xml and do not include any Model-Glue details.
Using Coldspring in Model-Glue
The next part is creating your top-level public Model-Glue application. For that you need your /index.cfm and /config/coldspring.xml files:
/index.cfm:
<cfsilent>
<cfset ModelGlue_APP_KEY = "public" />
<cfset ModelGlue_PARENT_BEAN_FACTORY = application.cs />
<cfset ModelGlue_LOCAL_COLDSPRING_PATH = expandPath(".") & "/config/coldspring.xml" />
</cfsilent><cfinclude template="/ModelGlue/unity/ModelGlue.cfm" />
<cfset structClear(variables) />
The structClear() is based on this advice. I started with it in place so I never experienced the slow down and crash described in the above link but it seemed like cheap insurance.
What we’ve done here is named our top level application “public” and told it to use the Coldspring instance we created in Application.cfc. We’ve also specified the Coldspring configuration file for this Model-Glue application (different than the coldspring file for your service layer!!!):
<beans>
<!-- SES -->
<import resource="/ModelGlue/actionpacks/SESURL/config/ColdSpring.xml" />
<!-- This is your Model-Glue configuration -->
<bean id="modelGlueConfiguration" class="ModelGlue.unity.framework.ModelGlueConfiguration">
<!-- Be sure to change reload to false when you go to production! -->
<property name="reload"><value>false</value></property>
<!-- Be sure to change debug to false when you go to production! -->
<property name="debug"><value>false</value></property>
<property name="defaultEvent"><value>public.index</value></property>
<property name="reloadKey"><value>init</value></property>
<property name="reloadPassword"><value>true</value></property>
<property name="viewMappings"><value>/views</value></property>
<property name="generatedViewMapping"><value>/views/generated</value></property>
<property name="configurationPath"><value>config/modelglue.xml</value></property>
<property name="scaffoldPath"><value>config/scaffolds/scaffolds.xml</value></property>
<property name="statePrecedence"><value>form</value></property>
<property name="eventValue"><value>event</value></property>
<property name="defaultTemplate"><value>/index.cfm</value></property>
<property name="defaultExceptionHandler"><value>exception</value></property>
<property name="defaultCacheTimeout"><value>5</value></property>
<property name="defaultScaffolds"><value>list,edit,view,commit,delete</value></property>
</bean>
</beans>
Like all Coldspring files, this one can include others like my search-engine-safe URL actionpack or anything else your Model-Glue application needs.
This is where I went wrong. Originally I had my beans.xml and this Coldspring.xml file combined into a single file because I started out with only a single MG application. When I moved to sub-applications, I left this file as-is and then created new a new sub-application config in /staff/config/coldspring.xml. This is where my phantom service layer was coming from! If you are going to use a parent bean factory, it is important that you create a service-layer-only configuration file that does not include any Model-Glue details!
After making this change, all 3 of my Model-Glue sub-applications were leveraging the same service layer and making a change to the visibility of an event in the staff application was instantly reflected in the public application. Success! Now, let’s configure a sub-application!
Create Sub-Applications
Now for my sub-application (which is simply a sub-directory named “staff”). First the /staff/index.cfm:
<cfsilent>
<cfset ModelGlue_APP_KEY = "staff" />
<cfset ModelGlue_PARENT_BEAN_FACTORY = application.cs />
<cfset ModelGlue_LOCAL_COLDSPRING_PATH = expandPath(".") & "/config/coldspring.xml" />
</cfsilent><cfinclude template="/ModelGlue/unity/ModelGlue.cfm" />
<cfset structClear(variables) />
Note this is identical other than the APP_KEY setting. Because we’re using expandPath and we’re putting the coldspring.xml in /staff/config/coldspring.xml, we don’t need to change anything else. Easy!
Now for the /staff/config/coldspring.xml:
<beans>
<!-- SES (for dev) -->
<import resource="/ModelGlue/actionpacks/SESURL/config/ColdSpring.xml" />
<!-- This is your Model-Glue configuration -->
<bean id="modelGlueConfiguration" class="ModelGlue.unity.framework.ModelGlueConfiguration">
<!-- Be sure to change reload to false when you go to production! -->
<property name="reload"><value>false</value></property>
<!-- Be sure to change debug to false when you go to production! -->
<property name="debug"><value>false</value></property>
<property name="defaultEvent"><value>dashboard</value></property>
<property name="reloadKey"><value>reinit</value></property>
<property name="reloadPassword"><value>true</value></property>
<property name="viewMappings"><value>/views</value></property>
<property name="generatedViewMapping"><value>/views/generated</value></property>
<property name="configurationPath"><value>config/modelglue.xml</value></property>
<property name="scaffoldPath"><value>config/scaffolds/scaffolds.xml</value></property>
<property name="statePrecedence"><value>form</value></property>
<property name="eventValue"><value>event</value></property>
<property name="defaultTemplate"><value>/staff/index.cfm</value></property>
<property name="defaultExceptionHandler"><value>exception</value></property>
<property name="defaultCacheTimeout"><value>5</value></property>
<property name="defaultScaffolds"><value>list,edit,view,commit,delete</value></property>
</bean>
</beans>
Looks verrrry similar doesn’t it? Only two lines changed in this sub-application, the defaultEvent and defaultTemplate:
<property name="defaultEvent"><value>dashboard</value></property>
<property name="defaultTemplate"><value>/staff/index.cfm</value></property>
Instead of public.index, we use dashboard for this application’s default event and because we want Model-Glue’s “myself” property to properly refer to the sub-directory, we’ve inserted the relative path to the index.cfm into the defaultTemplate. If you forgot to do this, your “myself” value would refer back to the top-level public application which is probably not what you intended.
Conclusion
Sub-applications in Model-Glue are a helpful way of breaking up your application to make each piece easier to develop, debug and manage but because this approach is not well documented, it can be easy to misconfigure your parent bean factory leading to strange behavior. This post gives you a step-by-step method for setting up two Model-Glue applications that share a single service-layer managed by Coldspring and Transfer.
Hopefully this howto leaves you with more hair than I have.
Paul Marcotte said:
on May 29, 2008 at 7:25 pm
Brian,
Thanks for posting a detailed example on creating a MG/CS/Transfer app with sub-applications. I’m going to be working with one very shortly and this is invaluable information. I’m glad that you we’re finally able to get past the issues! Count yourself in as a seasoned (albeit battle weary) veteran.
Jimmy said:
on July 22, 2008 at 5:17 pm
Hi Brian,
Wanted to drop a line and let you know that this post has been VERY helpful and have no doubt saved me several hours of banging my head against a wall.
My brain and I thank you!
Orange is my favorite color » Blog Archive » Sharing Application scope between multiple logical applications said:
on October 24, 2008 at 11:57 am
[...] it tries /api/config/beans.xml which doesn’t exist. I actually have this same problem with Model-Glue sub-applications where, upon reinit, I must hit the top-level application first or else they fail with the same [...]