This is a step-by-step example on how to setup Coldspring and Transfer to inject dependencies into your Transfer decorators. Normally Coldspring can only help with defined beans rather than ones you get via transfer.get() or transfer.new(). Mark added a bridge that lets them work together but I found his example too brief to understand. With his help, I have it working in my MG/CS/Transfer app. It’s easier than it seems.
Updated 1/11/08 – based on input from Sean Corfield, I’ve revised the setter method to behave more like ColdSpring and eliminate the case/switch statement.
Read on:
The goal
I’m going to use an example from my application today, where I want to make an AttendeeService available in my member bean (decorator) so that I can ask the member to give me its registration history. I want the bean to be smarter. Currently I do:
attendeeService.getRegistrationsByMember(member)
And I want to do:
member.getRegistrations();
So we have attendeeService.cfc, member.cfc, and coldspring.xml. We are going to add one new file, called injectorObserver.cfc to the mix, add a few new methods to the bean/decorator, do a little coldspring configuration and we’ll be good.
The Code
Here’s the relevant code from each file. First, we create the ability to set/get the attendee service on my member bean. So add these two methods to member.cfc:
<cffunction name="setAttendeeService" access="public" returntype="void" output="false">
<cfargument name="AttendeeService" type="any" required="true">
<cfset variables.AttendeeService = arguments.AttendeeService />
</cffunction>
<cffunction name="getAttendeeService" access="public" returntype="any" output="false">
<cfreturn variables.AttendeeService />
</cffunction>
Pretty standard for Coldspring. Now, create a new file called injectorObserver.cfc. This filename is totally arbitrary as is its location. We are creating an object that will monitor when a new TransferObject is created and dynamically inject the services we desire into it on the fly.
Note: Transfer decorators can have an optional configure() method that is run right after they are created. These injected services are added immediately after configure() and are not available during configure().
injectorObserver.cfc:
<cfcomponent displayname="injectorObserver" hint="Used to inject service dependencies into transfer decorator objects" output="false">
<cffunction name="init" access="public" output="false" returntype="any">
<cfargument name="transfer" type="transfer.com.Transfer" required="true" />
<cfset var serviceName = "" />
<cfset variables.services = structNew() />
<cfloop item="serviceName" collection="#arguments#">
<cfset variables.services[serviceName] = arguments[serviceName] />
</cfloop>
<cfset arguments.transfer.addAfterNewObserver(this) />
</cffunction>
<cffunction name="actionAfterNewTransferEvent" hint="Do something on the new object" access="public" returntype="void" output="false">
<cfargument name="event" hint="" type="transfer.com.events.TransferEvent" required="Yes">
<cfset var obj = arguments.event.getTransferObject() />
<cfset var serviceName = "" />
<cfloop item="serviceName" collection="#variables.services#">
<cfif structKeyExists(obj, "set#serviceName#")>
<cfinvoke component="#obj#" method="set#serviceName#">
<cfinvokeargument name="#serviceName#" value="#variables.services[serviceName]#" />
</cfinvoke>
</cfif>
</cfloop>
</cffunction>
</cfcomponent>
The name of the method, actionAfterNewTransferEvent, is fixed. That’s what Transfer looks for. The method looks at the decorator-to-be and searches for any methods that match the set[InjectedObject]() naming scheme.
Coldspring Config
Now this injectorObserver must exist if it’s going to do anything for us. So we have to tell Coldspring to create it on load. Add this to your coldspring.xml:
<bean id="injectorObserver" class="path.to.injectorObserver" lazy-init="false">
<constructor-arg name="transfer"><bean id="transfer" factory-bean="transfer" factory-method="getTransfer" /></constructor-arg>
<constructor-arg name="attendeeService"><ref bean="attendeeService" /></constructor-arg>
</bean>
Because Coldspring creates beans on demand, we need to force it to instantiate injectorObserver so it’s available to watch for decorators that need injecting. The lazy-init tells Coldspring to create the bean on startup and the init() method adds itself as an observer. From that point on, injectorObserver will be listening to Transfer for any created beans.
Test it!
You’re ready to go. Now just get the member bean:
<cfset member = transfer.get("member.member", 1) />
<cfdump var="#member.getAttendeeService()#" />
And this shows me the methods of my AttendeeService. Now you can inject your service layer into your beans!
If you want to inject more services, just create more set/get pairs in the decorator and tell Coldspring to pass them to injectorObserver’s init(). The bean will automatically pick it up.
Update 1/2009 – I finally switched from my homebrewed code to Brian Kotek’s BeanInjector and TDOBeanInjector from his coldspringutils project on RIAForge and both are working great.
Paul Marcotte said:
on January 9, 2008 at 10:52 pm
Thanks for posting this Brian. I just finished a similar, but less robust, solution. I like yours a *lot* more.
Brian Rinaldi said:
on January 10, 2008 at 5:58 am
Interesting. Though I am not sure I agree with the architecture. To me you would pass the user to the AttendeeService and see what he is registered for. Seems like knowing his registrations is beyond the scope of what a user should know (plus I just don’t like the idea of making a bean dependent on a service).
Brian Kotek said:
on January 10, 2008 at 8:55 am
I have a fully dyanamic dependency injection observer for Transfer Objects that is in the hands of Mark and Chris Scott now. If they agree that it looks all right, it will either be included in the next version of ColdSpring or I’ll put it up on RIAForge. It autowires your decorators without you having to do anything.
Brian, regarding your comment about beans talking to services, what is your concern with this? I do it all the time. It just means the beans can be smart. In this example, I’d say it’s perfectly fine to ask a user about their registrations. “Hey Bob, what classes are you signed up for?” as opposed to “Hey Mr. Manager, what classes is that guy Bob signed up for?”
Brian Rinaldi said:
on January 10, 2008 at 9:03 am
I guess I see your point after giving it a little bit of thought. Perhaps its not such a bad design decision, though my gut still leans towards a user not knowing this. It just seems to me it is the registration systems job to know what classes people are signed up for and not a users job…but it is a gut reaction.
brian said:
on January 10, 2008 at 9:39 am
@Brian R – I initially agreed with you. I built my service layer very independent so nothing was too tightly coupled. However, having read that thread on the MG list and talking with Brian K, Sean and others, I can see the benefit of “smarter” beans. It certainly makes development easier but I also worry about dependencies.
Ultimately, if the getRegistrations() request is routed back through the service layer, then really all you’ve done is created a shortcut from the member bean to the service layer which is a convenience.
I still have some questions for Brian K and others that I’ll post to the MG listserv this morning before I go crazy.
brian said:
on January 10, 2008 at 10:55 am
@Brian K – I hope the dynamic injector you sent Mark and Chris is released soon. One problem with these frameworks is staying on top of changes can be difficult… especially since I’m running BERs of everything here. Although this injectorObserver method works, it does feel a little funky to be doing a case/switch on the class name.