Model-Glue: What the hell is autowiring?

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.

[Read more on Joe Rinehart's blog]

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!!

TweetBacks
Comments (Comment Moderation is enabled. Your comment will not appear until approved.)
© 2018 Joe Rinehart
BlogCFC was created by Raymond Camden. This blog is running version 5.9.3.006.