Examining LightWire Dependency Injection

Posted on Oct 25, 2006

As you might have caught from Peter Bell's recent post, I was toying with his LightWire lightweight dependency-injection framework. For handling a concept that sounds complicated, LightWire is pretty simple. The component itself was pretty short and I actually managed to shorten it further I believe by simply cleaning up some code issues. Nonetheless, it did exactly what it advertised with almost no overhead, performance-wise. Granted, this does not have anything near the feature-set of ColdSpring, but it it achieves the basic necessities very simply and efficiently (it is nothing more than a single component and programmatic config file). It also is geared towards supporting "transient" objects, which I will cover more later.The Nature of the Test
What I did was to write a very simple test-bed for using LightWire. I had three components, a service component, a data access object (DAO) and a simple bean (none of which had much actual code going on, that wasn't the point of the test). My goal was to have LightWire instantiate my DAO with a string value for the DSN and then inject that into the service object. (One note, all of my components were sitting in the root of my CFCs directory, which at first LightWire did not like, but I have since fixed that bug as you will see here). For the moment we will not do anything with the bean.

Setting the Config
One improvement I suggested to Peter was that, while his config is not complicated once you know your way around, it is poorly organized. To set up each object and its dependencies, you need to go edit several different sections of the config. Peter was very receptive to this idea and I expect to see some changes coming soon, specifically organizing it so all the configurations for a specific object are grouped together (possibly renaming his structs to make more sense with regards to that grouping style). Nonetheless, in the current config, you first need to set the base path for your components in the first section; mine is in a subfolder called test and has a com folder for components:

variables.LightWire.BaseClassPath = "test.com";

Next, since the DAO and service are singletons, we add their sub-path information to the variables.class.singleton structure like so (in case you are unfamiliar, a singleton, in very simple terms, is a component that will be loaded once and kept in application scope):

variables.Class.Singleton.testService.Path = ""; variables.Class.Singleton.testDAO.Path = "";

These are both empty since, as I said, my components are sitting in the component root (i.e. /test/com/ from above), so you can now leave this empty. If they were user components in a user subdirectory, obviously the value here would be "user".

Now I go down to the dependencies section. Lightwire simply sets a comma-delimited list of the objects using the names set above in the Singleton structure). So for my test service to contain my testDAO, I would add the following:

variables.Dependencies.testService = "testDAO";

Lastly, my testDAO requires a string value for the DSN. I add any configuration values like this at the bottom of the config as follows:

variables.testDAO.dsn = "mydsn";

The one nice feature of using a programmatic config, even though I think it is less self explanatory, is that it would be easy to use ColdFusion code to set these kinds of values, even conditionally.

Creating the Components
LightWire always assumes (at this point) that your dependencies will be injected using constructor arguments. So, to add the DSN string to my DAO, I need to add an init function:

<cffunction name="init" output="false" access="public" returntype="testDAO">    <cfargument name="dsn" type="string" required="true" />           <cfset variables.dsn = arguments.dsn />    <cfreturn this /> </cffunction>

Note that the name of the argument must be the same as the structure key (i.e. variables.testDAO.dsn - the key is dsn). Likewise, I would initialize my service component like so:

<cffunction name="init" output="false" access="public" returntype="testService">    <cfargument name="testDAO" required="true" type="test.com.testDAO" />           <cfset variables.testDAO = arguments.testDAO />    <cfreturn this /> </cffunction>

Initializing and Using Lightwire
In my application.cfm/cfc I would put this code in for when my application starts:

<cfset configFilePath = "/test/LightWireConfig.cfm"> <cfset application.LightWire = CreateObject("component","test.LightWire").Init(configFilePath)>

Obviously configFilePath is the location of my LightWire config file, which is the only argument required or otherwise when initializing LightWire at the moment (I have also suggested some small changes here as well to simplify the config).

I can now get my service component and test it as follows:

<cfset testService = application.lightWire.getSingleton("testService") /> <cfoutput> Test Service: #testService.getTestDAO().getDSN()# </cfoutput>

This will properly output the value ("mysdsn" set above) of the DSN string in the DAO. Also, if you dump the service, you will see that it does indeed contain the DAO.

Transient Objects
Peter likes to use a variation of the ActiveRecord pattern for his beans. It is not something I do (I am no fan of ActiveRecord), but many people do. This would mean that your bean has dependencies, but you cannot load a single copy of a bean into the application scope, because many instances of this bean will need to exist depending on the context in which they are used. ColdSpring *can* do this (it is after all, a full-fledged factory), but it is not recommended (and I believe has some limitations in this regard, but I cannot remember specifics). In the case of an ActiveRecord bean, it would likely require your DAO for CRUD operations. You would add the following code (not in this particular order in the current config):

variables.Class.Transient.testObject.Path = ""; variables.Dependencies.testObject = "testDAO";

Now in your code, you can call this bean like so:

<cfset testObject = application.lightWire.getTransient("testObject") /> <cfoutput> Test Object: #testObject.getTestDAO().getDSN()# </cfoutput>

The output will be the same as the above non-transient example, but the difference is that this object is created on every call to LightWire, while the prior example is not. This allows you to populate this bean without affecting other threads that may be using it.

When Would You Use LightWire?
As you can see, LightWire, as it currently stands, can handle simple dependency injection well. For your small application, this may be all that is necessary, in which case, perhaps LightWire is an option. The benefit in this case is that it is nothing more than a single CFC and a single config, so it is very simple to package it with your application (say if you distributed or sold your application). However, if you have complicated dependency relationships, LightWire, as it currently stands would probably not fit your needs. Instead, use ColdSpring.

Also, there exists at the current time, a limitation because of how LightWire injects objects, that you cannot call any methods on your injected object within the config. While this isn't a common scenario in my experience, it does happen often enough to warrant correcting this limitation, and Peter has said he expects to have this fixed shortly. (see this post for details)

Additionally, if you like things like ActiveRecord or generating your application like Peter, his application is optimized for those use-cases. I also think, if the configuration can be cleaned up and simplified, this is a great tool for understanding the basics of dependency injection, which once you get past the intimidating sounding name, is a pretty basic concept. LightWire, in focusing only on the basics of the problem, I think can make the concept seem less complex for someone beginning to understand the need for DI. I am looking forward to seeing where Peter takes his project.

Comments

There are currently no comments for this entry...be the first!

Write your comment



(it will not be displayed)





About

My name is Brian Rinaldi and I am the Web Community Manager for Flash Platform at Adobe. I am a regular blogger, speaker and author. I also founded RIA Unleashed conference in Boston. The views expressed on this site are my own & not those of my employer.


© 2011 - Brian Rinaldi