Mar 21 2006

Model-Glue: What the hell is autowiring?

Posted by Joe Rinehart at 8:53 AM
11 comments
- Categories: ColdFusion MX | Model-Glue

Scott Stroz dropped me an IM yesterday asking "What the hell is autowiring?" It's a pretty good question, and one that I haven't done a good job explaining.

So, here we go: the evolution of autowiring. In the beginning...

...there was Model-Glue and ChiliBeans. It was good. You could ask for a ConfigBean, and use it as configuration. That was handy, but it didn't really get the full point of IoC: the bean could be a lot more than a representation of a datasource, etc.

So I shifted the branding message...

GetConfigBean ("Get Configuration Bean") slowly became GetConfigBean ("Get Configured Bean"). To use the (overused now!) contact manager example, instead of asking for something like GetConfigBean("datasource.xml"), you could ask for something like GetConfigBean("ContactManagerService.xml"), and ChiliBeans would load a ContactManagerService with methods like saveContact(), loadContact(), and listContacts().

This change was enabled by allowing properties defined in ChiliBeans to refer to other chilibeans xml files. This means you could define your ContactService as a config'd bean and tell it to use the bean defined by Datasource.xml as its datasource.

In code, this represented a shift from this:

<!--- ContactController.cfc --->
<cffunction name="Init">
<cfargument name="ModelGlue">
<cfset super.Init(arguments.ModelGlue) />

<!--- Get our datasource --->
<cfset var Datasource= getModelGlue().getConfigBean("Datasource.xml") />

<!--- Create the service and init it with the datasource --->
<cfset variables.ContactService = createObject("component", "contactmanager.model.service.Contactservice").init(variables.datasource) />
<cfreturn this />
</cffunction>

To this:

<!--- ContactController.cfc --->
<cffunction name="Init">
<cfargument name="ModelGlue">
<cfset super.Init(arguments.ModelGlue) />

<!--- Create the service--->
<cfset variables.ContactService = getModelGlue().getConfigBean("ContactService.xml") />

<cfreturn this />
</cffunction>

Then ColdSpring showed up...

One issue I had with Chilibeans was that it was tied directly to the filesystem. ColdSpring solves this by removing the filesystem nature of its BeanFactory - generally, a file is loaded when the factory is constructed that contains all of the beans the factory can create. That means I could replace the two files needed for my existing setup (Datasource.xml and ContactService.xml) with a single definition file that shows ContactService needing Datasource in its constructor:

<beans>
<bean id="ContactService" class="contactmanager.model.service.Contactservice">
<constructor-arg name="Datasource">
<ref bean="Datasource" />
</constructor-arg>
</bean>

<bean id="Datasource" class="contactmanager.model.data.Datasource">
<property name="DSN">
<value>ContactManager</value>
</property>
</bean>
</beans>

It's a very small change in code:

<!--- Create the service--->
<cfset variables.ContactService = getModelGlue().getConfigBean("ContactService.xml") />

Becomes:

<!--- Create the service--->
<cfset variables.ContactService = getModelGlue().getConfigBean("ContactService") />

However, my controller is no longer tied to the filesystem, and it has a much more powerful BeanFactory behind it.

Life was good, but repetitive...

If my contact manager grew, I'd probably start needing things like an EmailService, an AuthenticationService, a LoggingService...and I'd need to add some constructor code for each of these. My constructor would start to look like this:

<!--- ContactController.cfc --->
<cffunction name="Init">
<cfargument name="ModelGlue">
<cfset super.Init(arguments.ModelGlue) />

<!--- Create services --->
<cfset variables.ContactService = getModelGlue().getConfigBean("ContactService") />
<cfset variables.EmailService= getModelGlue().getConfigBean("EmailService") />
<cfset variables.AuthenticationService= getModelGlue().getConfigBean("AuthenticationService") />
<cfset variables.LoggingService= getModelGlue().getConfigBean("LoggingService") />

<cfreturn this />
</cffunction>

Behold, the autowire...

Now, autowiring doesn't do much to cut down on this repetitive code. What it does do, however, is delegate the entire creation of these services to something else. It serves to dumb down your controller - in MVC, a dumb, skinny controller is better than a fat, smart controller.

To make my ContactController an "autowired" controller, I'd do the following:

  1. Make sure I was using the Model-Glue BER, available at svn://clearsoftware.net/clearsoftware.net . (Yes, 1.1 is coming, so this'll remove this step. I just need to finish unpacking and my cfunited presos.)
  2. Write a "setter" function for each of these services named Set[BeanIdFromColdSpring]:

    <cffunction name="setContactService">
    <cfargument name="contactService">
    <cfset variables.contactService = arguments.contactService />
    </cffunction>
    <cffunction name="setEmailService">
    <cfargument name="EmailService">
    <cfset variables.EmailService= arguments.EmailService/>
    </cffunction>
    <!--- And so on... --->
  3. Delete my constructor ("init" method) entirely.

Now, when Model-Glue loads your controller, it'll look at it for any methods named "Set[something]", and see if ColdSpring has a bean named "something". If it does, it'll ask ColdSpring for that bean, and call the Set[Something] method, passing it the bean.

Wait - autowiring is more code?

Yep, I did a double-take too. It *is* more code to autowire. However, as your app grows, you're likely to start to "mash up" more services. I have one application that has about nine different "NnnnService" CFCs, and each controller needs its own subset of those (sometimes one, sometimes all nine!).

What I've done there is move all of these "set[Something" functions to a file called /model/mixin/applicationServices.cfm that each of my controllers (becoming a "static mixin"). That way, if a new service pops up, I add a setter function to one file, and it's available in all services! Now that's much nicer writing init() plumbing code in specific controllers!!

Comments

Scott Stroz

Scott Stroz wrote on 03/21/06 9:04 AM

Thank you kind sir.
Matt Williams

Matt Williams wrote on 03/21/06 11:48 AM

Do I understand that with your mixin, all possible services have access to all ColdSpring beans? Is that not too much unnecessary overhead?

I have a PersonService that uses a PersonDAO and PersonGateway (beans defined in ColdSpring). But I wouldn't want to inject those into my ProductService.
Dave Ross

Dave Ross wrote on 03/22/06 3:03 PM

Matt,

It doesn't mean every &lt;bean/&gt; in the factory, it would only mean beans with corresponding setter methods that you've added to your mixin. Regardless, I don't think there's much overhead at all - it's just object references being passed around.

-Dave
Brandon Whitehall

Brandon Whitehall wrote on 03/22/06 3:06 PM

To my way of thinking, anything &quot;auto&quot; should cause the user less work(coding), not more. I suggest specification of the required services by name instead of code. Within the &lt;controllers&gt; section of the ModelGlue.xml file, specify the required services and have ModelGlue / ColdSpring instantiate the services when the controller is instantiated, storing them in the controller's variables scope. Using your example, it might look like

&lt;controller name=&quot;myController&quot; type=&quot;myController&quot;&gt;
&lt;services=&quot;ContactService,EmailService,AuthenticationService&quot;/&gt;
&lt;/controller&gt; or maybe

&lt;controller name=&quot;myController&quot; type=&quot;myController&quot;&gt;
&lt;services&gt;
&lt;service=&quot;ContactService&quot;/&gt;
&lt;service=&quot;EmailService&quot;/&gt;
&lt;/services&gt;
&lt;/controller&gt;

Using this approach, the controller is even dumber than it was using the setNNN code, and the user has no code to create to support autowiring. To quote Martha Stewart, &quot;This is a good thing.&quot;
jason sheedy

jason sheedy wrote on 03/27/06 11:47 PM

Joe, I'm really shying away from this heavy cold spring integration with MG. Coldspring is kinda cool, but I don't like the dependencies it creates between your service/manager objects. i.e. setter methods, etc.

I impliment a simple solution that takes care of all the inter component dependencies that removes the need for all this extra overhead. It also ensure that your controllers are relatively 'dumb'. I use an application facade.

&lt;cfset variables.appFacade = GetModelGlue().getSingleton(&quot;component&quot;, &quot;org.common.appFacade&quot;) /&gt;

&lt;cfset variables.userManager = variables.appFacade.getUserManager() /&gt;

The initialisation of the appFacade can be handled however you like. i.e. in application.cfc, or an MG onrequeststart method.

It provides all the simple getter interafaces you need to your business model objects.
jason sheedy

jason sheedy wrote on 03/27/06 11:50 PM

Also, forgot to mention...the appFacade can be used by my webservices, scheduled tasks, event gateways using the same interfaces.
Joe Rinehart

Joe Rinehart wrote on 03/28/06 7:40 AM

Hey Dave,


&gt; That's not *exactly* what you want to do though is it? Don't you need
&gt; to keep the init() method itself (for the super.Init(arguments.ModelGlue) bit),
&gt; just not the &quot;custom&quot; code you added? Or does ColdSpring mitigate the need
&gt; for the init() method whatsoever?

It mitigates the need entirely - the base controller's constructor will be called in place of an overriding constructor.
Joe Rinehart

Joe Rinehart wrote on 03/28/06 7:43 AM

Hey Brandon,

&gt; Using this approach, the controller is even dumber than it was using the setNNN code

Yeah, I've thought about something along that line - even more extreme, using a ServiceLocator like some other MVC frameworks. However, what I don't like about the way you described it is that, at some point, the ModelGlue.xml is stepping in and acting &quot;in place of&quot; (by replacing SetNNN methods) the controller's API.

Using a true ServiceLocator isn't necessary, either: ColdSpring already gives the controller the ability to ask for something by an arbitrary name.
Joe Rinehart

Joe Rinehart wrote on 03/28/06 7:49 AM

Hey Jason,

&gt; I don't like the dependencies it creates between your service/manager
&gt; objects. i.e. setter methods, etc.

Actually, ColdSpring somewhat eliminates these dependencies:

&gt; I use an application facade.

&gt; &lt;cfset variables.appFacade = GetModelGlue().getSingleton(&quot;
&gt; component&quot;, &quot;org.common.appFacade&quot;) /&gt;

Your app is now bound to org.common.appFacade - what ColdSpring does through the &quot;setNNN&quot; method is *remove* the dependency on org.common.appFacade, instead creating a dependency that &quot;something&quot; passed into that method will act the way the org.common.appFacade should act, regardless of whether or not it really is the org.common.appFacade class. By doing this, I could switch to org.uncommon.hardcodedAppFacadeThatAlwaysWorksBecauseIHaveABigClientDemoTomorrow and my application wouldn't be any wiser.

&gt; &lt;cfset variables.userManager = variables.appFacade.
&gt; getUserManager() /

How does org.common.appFacade know where to find the userManager class? Does it do createObject(&quot;component&quot;, &quot;org.common.userManager&quot;).init()? That's another thing ColdSpring can help with - eliminating that dependency.
John Allen

John Allen wrote on 03/29/06 4:59 PM

This stuff is great.

Im getting refused connections to the BER

&quot;Error: Can't connect to host 'clearsoftware.net': No connection could be made because the target machine actively refused it.&quot;

Any ideas? thanks mucho
Jason Sheedy

Jason Sheedy wrote on 04/03/06 7:20 AM

Hi Joe,
sorry I didn't reply sooner, but I didn't get CC'ed in on your comments.

Coldspring is really cool, yes, but I think the benifit you gain buy the amount of decoupling it provides is outweighed buy the fact that you're now coupled to an entire other framework/release cycle/etc. You also need to create specific setters in you service/manager objects to accomodate it. What happens if you move to another framework? You've gotta change you business model too. In my honest opinion, I reckon the business model should be framework independent.

It's true that by using an appFacade I create a dependency in the controller, but only a very minor one. If you're going to do it, that's the place for it. My appFacade manages all the dependencies between tbe classes in the business model and initialises them all at start up.

The other cool spin off is that I can share one instance of my model between MG, webservices, scheduled tasks and event gateways. :)

Write your comment



(it will not be displayed)