Adding Todo Lists (Autowiring our Handler) - ColdBox Series Part 8c

As Luis stated in the comments section of my last post, we need to set up autowiring in our handler component. This is really cool stuff. I did not do this the first time around because my post was getting to long and because I wanted to demonstrate both ways of doing it.

What is autowiring?

When you are using object factories (like Lightwire and ColdSpring) in your ColdBox applications, you can set up dependency injection for handlers, plugins and interceptors. This is done using the Autowire Interceptor.

So what dependency do we have in our List.cfc handler? Here is what it looks like now:


<cfcomponent display="List Handlers" output="false" extends="coldbox.system.eventhandler">
    <cffunction name="addList" access="public" returntype="void" output="false">
        <cfargument name="Event" required="true" type="coldbox.system.beans.requestcontext" />
        
        <cfset event.setValue('xehDoit', 'list.doAddList') />
        <cfset event.setValue('btn', 'Add')>
        <cfset event.setView('listForm') />
    </cffunction>
    
    <cffunction name="doAddList" access="public" returntype="void" output="false">
        <cfargument name="Event" required="true" type="coldbox.system.beans.requestcontext" />
        
        <cfset var todoService = getPlugin("ioc").getBean("todoService") />
        
    </cffunction>
</cfcomponent>

Do you see the getPlugin() call in the doListAdd() handler? That is calling an object that the handler depends on to function properly. And if we were able to see into the future we would see that most, if not all, of the methods in this handler will also need that object. What a perfect opportunity for setting up autowiring. Wouldn't it be nice if we did not need to call this plugin for every method?


    <cfset var todoService = getPlugin("ioc").getBean("todoService") />

With autowiring, the todoService will be automatically injected into our variables scope (or another scope) and will always be available. So, first we need to go to our coldbox.xml.cfm file in our /config directory and set up the autowire interceptor.

Setting up autowiring

In the coldbox.xml.cfm file, find the <interceptors> tags and insert this block in between the beginning and end tags.


    <Interceptor class="coldbox.system.interceptors.autowire">
        <Property name="debugMode">true</Property>
        <Property name="completeDIMethodName">onDICOmplete</Property>
        <Property name="enableSetterInjection">false</Property>
    </Interceptor>

This tells ColdBox to use the autowire interceptor. It also sets up an optional method that will automatically be called after the dependency injection is completed. We won't be using that today. And the last property disables setter injection. With setter injection disabled, we only need a cfproperty tag to let ColdBox know what to insert. Honestly, I am not sure why we would want to turn it on. Maybe someone else knows and would like to enlighten us in the comments.

Now that we have modified the coldbox.xml.cfm file, we can change our handler to support autowiring. This is done with a bit of metadata.


<cfcomponent display="List Handlers" autowire="true" output="false" extends="coldbox.system.eventhandler">

Now ColdBox knows to autowire this handler, next we tell it what to inject. So we add a <cfproperty> tag in the pseudo-constructor area of the component. The pseudo-constructor area is in between the <cfrcomponent> tag and the first function.


    <cfproperty name="todoService" type="ioc" scope="variables" />

There. That was simple. Now the todoService (the "name" attribute must match the name as defined in ColdSpring or Lightwire) will automatically be placed into the variables scope when this handler is called. So we can get rid of the long getPlugin() call.

Conclusion

This may not seem like it is that helpful, but it is. When you consider that in a large application, your handlers may have several dependencies per handler, and each handler may have dozens of methods, it can make for much cleaner, easier to manage code.

Comments
Luis Majano's Gravatar Not only is it helpful jason, but some other benefits are:

1) Your object dependencies are all nicely laid out as properties on top. You do not need to scan the object for setters of any kind.
2) You do not need to create additional getters/setters if your object doesn't really need to create them. Why create them just for Dependency Injection? Objects should be shy!

Those are my main reasons to prefer DI via conventions.
# Posted By Luis Majano | 11/11/08 4:05 PM
Jason Dean's Gravatar @luis, thanks for the input. Good to know. I had not considered those things, but they make good sense.
# Posted By Jason Dean | 11/11/08 6:34 PM
John Whish's Gravatar Hi Jason, great series! I think that I prefer using setter dependencies instead of defining them with cfproperty setters. This is partly because I'm used to how ColdSpring does this and I also like having a clear API (with hints) especially when I'm extending objects.
The nice thing about ColdBox is you can use which ever approach suits your need. I tend to let ColdSpring handle all dependency injection, so that I can see all dependencies in one config file (I don't use ColdSpring's autowire capabilities).
# Posted By John Whish | 4/6/09 2:17 PM
Jason Dean's Gravatar @John, Thanks. Glad you like it.

I can see what you are saying about the setting injection. I too am glad that we have the choice. For injecting into handlers, I feel like the autowiring is best for me, since I am not worried about handler portability or using the handlers without the framework.
# Posted By Jason Dean | 4/6/09 2:43 PM
Luis Majano's Gravatar @John
>I also like having a clear API (with hints) especially when I'm extending objects
You still have this with cfproperty. You can add hints, and extra metadata to cfproperty like you would on functions. Also, CFDoc or the CFCViewer plugin can document your objects and you can get a clear definition of a class' dependencies.

On another note, setters and getters are to me useless, unless they provide a specifc behavior to an object. I prefer my objects to be SHY and only expose what they need to. Dependency injection is not a reason for me to expose setters when they can be injected without exposing setters or getters. However, there are always cases where you expose them. But, like you said it, choice is the key, as everybody has choices and ways to do it.

I guess my preference has always been to create Shy objects.
# Posted By Luis Majano | 4/6/09 2:50 PM
John Whish's Gravatar Thanks for the feedback @Jason and @Luis :)

Whilst I've looked at ColdBox before I've never really got into the details (although I've used Frameworks for a long time), so I guess I'm largely stuck in my ways! I think you make a good point Luis about making object SHY. It's just odd to me (and I'm happy to admit many things I used to find odd I do all the time now!) to have ColdSpring and ColdBox doing autowiring. Having said that I do use Paul Marcotte's Transient Factory with Brian Kotek's Bean Injector as well as ColdSpring so I'm already using multiple tools for DI!

One question I do have is in the docs, it uses "instance" instead of "variables" for the cfproperty tag. Are they interchangeable?
# Posted By John Whish | 4/7/09 2:10 AM
Jason Dean's Gravatar @John,

If you use "variables" it places it injected objects into the variables scope. If you use "instance" it places them in an "instance" struct in the variables scope.
# Posted By Jason Dean | 4/7/09 8:08 AM
John Whish's Gravatar Thanks Jason, that's pretty neat. What happens if you specify "variables.instance" :P

Just need to work out why my sidebar has vanished now...
# Posted By John Whish | 4/7/09 8:36 AM
Paul Baylis's Gravatar I'm getting an error running http://localhost:8500/china-buy/?event=list.addLis...

========================================================
Message:
ERROR 10:42:57.953 AM coldbox.system.interceptors.Autowire
Error autowiring china-buy.handlers.list. Bean creation exception in model.todos.todoService Could not find the ColdFusion Component or Interface model.todos.todoService.:Ensure that the name is correct and that the component or interface exists.
========================================================

Yes, I DO have a cfc named "todoService.cfc" in wwwroot/china-buy/model/todos/

My config looks like this:
-------------------------------
<?xml version="1.0" encoding="UTF-8"?>

<Config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance";
   xsi:noNamespaceSchemaLocation="http://www.coldbox.org/schema/config_3.0.0.xsd&quo...;
   <Settings>
      
      <!-- Application Setup-->
      <Setting name="AppName" value="china-buy"/>
      <Setting name="EventName" value="event" />
      
      <!-- Development Settings -->
      <Setting name="DebugMode" value="true"/>
      <Setting name="DebugPassword" value=""/>
      <Setting name="ReinitPassword" value=""/>
      <Setting name="HandlersIndexAutoReload" value="true"/>
      <Setting name="ConfigAutoReload" value="false"/>
      
      <!-- Implicit Events -->
      <Setting name="DefaultEvent" value="General.index"/>
      <Setting name="RequestStartHandler" value="Main.onRequestStart"/>
      <Setting name="RequestEndHandler" value=""/>
      <Setting name="ApplicationStartHandler" value="Main.onAppInit"/>
      <Setting name="SessionStartHandler" value=""/>
      <Setting name="SessionEndHandler" value=""/>
      <Setting name="MissingTemplateHandler" value=""/>
      
      <!-- Extension Points -->
      <Setting name="UDFLibraryFile" value="includes/helpers/ApplicationHelper.cfm" />
      <Setting name="PluginsExternalLocation" value="" />
      <Setting name="ViewsExternalLocation" value=""/>
      <Setting name="LayoutsExternalLocation" value="" />
      <Setting name="HandlersExternalLocation" value="" />
      <Setting name="RequestContextDecorator" value=""/>
      
      <!-- Error/Exception Handling -->
      <Setting name="ExceptionHandler" value=""/>
      <Setting name="onInvalidEvent" value=""/>
      <Setting name="CustomErrorTemplate" value="" />
      
      <!-- Application Aspects -->
      <Setting name="HandlerCaching" value="false"/>
      <Setting name="EventCaching" value="false"/>
      <Setting name="ProxyReturnCollection" value="false"/>
      <Setting name="FlashURLPersistScope" value="session"/>

   </Settings>

   <!--Model Integration -->
   <Models>
      <DefinitionFile>config/ModelMappings.cfm</DefinitionFile>
   </Models>
   
   <LogBox>
      <Appender name="coldboxTracer" class="coldbox.system.logging.appenders.ColdboxTracerAppender" />
      <!-- Root Logger Definition -->
      <Root levelMin="FATAL" levelMax="INFO" appenders="*" />
      <!-- ColdBox Package Logging -->
      <Category name="coldbox.system" levelMax="INFO" />
   </LogBox>
   
   <Layouts>
      <DefaultLayout>Layout.Main.cfm</DefaultLayout>
   </Layouts>

   
<Interceptors>
    <!-- USE ENVIRONMENT CONTROL -->
    <Interceptor class="coldbox.system.interceptors.EnvironmentControl">
       <Property name="configFile">config/environments.xml.cfm</Property>
    </Interceptor>

      <!-- USE AUTOWIRING -->
      <Interceptor class="coldbox.system.interceptors.Autowire">
<Property name="debugMode">true</Property>
<Property name="completeDIMethodName">onDICOmplete</Property>
<Property name="enableSetterInjection">false</Property>
      </Interceptor>
      <!-- USE SES -->
      <Interceptor class="coldbox.system.interceptors.SES">
         <Property name="configFile">config/routes.cfm</Property>
      </Interceptor>
   </Interceptors>
   
   <!--IOC Integration-->
<IOC>
<Framework type="coldspring" reload="true" objectCaching="false">/config/coldspring.xml.cfm</Framework>
<!--<ParentFactory type="coldspring">definition file</ParentFactory>-->
</IOC>   
   
</Config>


My coldspring xml looks like this:
-----------------------------------------
<beans default-autowire="byName">
<bean id="todoGateway" class="model.todos.todoGateway">
<property name="dsn">
   <value>todos</value>
   </property>
</bean>

<bean id="todoService" class="model.todos.todoService">
<property name="todoGateway">
<ref bean="todoGateway" />
</property>
</bean>
</beans>


My handler looks like this:
----------------------------------
<cfcomponent display="List Handlers" output="false" extends="coldbox.system.eventhandler">
<cfproperty name="todoService" type="ioc" scope="variables" hint=""/>

<cffunction name="addList" access="public" returntype="void" output="false">
<cfargument name="Event" required="true" type="any" />
<cfset event.setValue('xehDoit', 'list.doAddList') />
<cfset event.setValue('btn', 'Add')>
<cfset event.setView('listForm') />
</cffunction>

<cffunction name="doAddList" access="public" returntype="void" output="false">
<cfargument name="Event" required="true" type="any" />

   <cfset var todoService = getPlugin("ioc").getBean("todoService") />
</cffunction>

</cfcomponent>
# Posted By Paul Baylis | 3/12/10 4:06 PM
Paul Baylis's Gravatar Minor Correction: In actuality, there is no leading slash in "/config/coldspring.xml.cfm" in the IOC block below. That's been removed.

<IOC>
<Framework type="coldspring" reload="true" objectCaching="false">/config/coldspring.xml.cfm</Framework>
<!--<ParentFactory type="coldspring">definition file</ParentFactory>-->
</IOC>
# Posted By Paul Baylis | 3/12/10 4:12 PM
Paul Baylis's Gravatar Any ideas ... please?
# Posted By Paul Baylis | 3/14/10 7:01 PM
Jason Dean's Gravatar @paul

Geez, don't I get weekends off?

;)

I suspect this is the same problem you were having with the coldbox event object. ColdFusion cannot find them because you are working in a subfolder.

Your model components are actually at china-buy.model.*, but you are referencing them at model.*

You can solve this in one of two way.

1. Set up a mapping called /model that points to expandPath('/china-buy/model')

2. Have all of your references point to china-buy.model.* (replace * with the Component name), example china-buy.model.todos.todoService

I would do option 1.

Hope this helps.
# Posted By Jason Dean | 3/14/10 7:11 PM
Paul Baylis's Gravatar Heh, it's Monday in New Zealand.
Thanks, all fixed. I decided to go for option 2, rather than create one mapping for "/model". After all, I have quite a few different apps in wwwroot.
By the way, GREAT tutorial. I'm getting there slowly.
# Posted By Paul Baylis | 3/14/10 9:12 PM
Jason Dean's Gravatar @paul,

If you create your mapping in Application.cfc instead of in the administrator, you can make it specific to that application.

Thanks, glad you like the tutorials.
# Posted By Jason Dean | 3/14/10 9:26 PM
BlogCFC was created by Raymond Camden. This blog is running version 5.9.1. Contact Blog Owner