Posted by Brad Wood
Nov 08, 2008 18:29:00 UTC
As we push ahead with our app using ColdBox and LightWire, we have been configuring our Dependency Injection as we go. DI is one of those things I'm pretty certain I see the benefit to, but I'm not sure how sold on it I am. At this point, we may only have 50 to 60 CFCs so perhaps we're just not deep enough in to see the real profit yet. The only real advantage I've seen to date is having all of our dependencies spelled out in one configuration file (with the exception of the stuff you are auto-wiring). Other than that, I don't know that I have really saved any code. That being said, I don't have any circular dependencies, and my dependency levels don't get much more than 2 or 3 levels deep right now so maybe I just need to be patient.If for no other reason than to save on code, we have been using constructor injection over setter injection. Even Martin Fowler states that he prefers constructor injection over setter injection if possible in order to more clearly define what is necessary to create a valid object. ("Constructor versus Setter Injection" section) Also it allows you to keep your injected properties immutable by not having to expose public setter methods for them. Constructor Injection requires that you accept each injected property during instantiation of your component as a parameter to your constructor (generally init()) and subsequently set it into your CFC (preferably in the variables scope to make it a private property). This will cost you about two lines of code in your CFC. Setter Injection requires that you provide a public setter method to set each necessary property into the object. The object is created in one step, and then the setter methods are called to finish populating the object. This approach requires you to code all of the setters, but it is the only way to satisfy circular dependencies (where two components require a reference to each other). One of my first problems with DI is that I can't directly create my transient objects, but rather need a reference to my object factory (LightWire in this case) to create my objects for me and return them already stuffed full of dependant goodness. Of course, that fact in itself isn't the issue. I have decided that all transient objects will be created inside of the appropriate service component. (My Event Handler asks the userService to track the current user, which creates an instance of the user object for storage in session) Since LightWire is baked into ColdBox, I must inject a dependency to the ColdBox framework into each of my services. There goes the concept of a loosely coupled controller and server layer! So, on to the original topic of this post: constructor injection and inheritance. For the sake of an example, let's consider two components-- widget.cfc and customWidget.cfc. customWidget is a specialized form of widget, providing expanded functionality and additional properties over the base class. Therefore, customWidget extends widget and its init() follows the familiar super.init() pattern. Let's say that widget has widgetDAO, and widgetService injected into it via a parameter to the init function. The customWidget class has an additional dependency to the doohickeyService component. Now, what happens on instantiation of a customWidget object is LightWire only calls customWidget.init() and that one method is the only window of opportunity that our DI engine will have to inject in the necessary pieces. This means that the customWidget.init() method needs to accept not only the doohickeyService object, but the widgetDAO and widgetService as well which it will promptly turn around and pass into the super.init() call. The long and short of this is that constructor injection mandates a component have knowledge of all its ancestor components' dependencies and be prepared to accept all those dependencies and pass them on. Maybe it isn't so bad for a class to know a little about the class it is extending, but it makes me uncomfortable. In addition to the programmatic duplication, my DI configuration also has duplicated pieces. Since widget and customWidget can both be instantiated directly, I must set up a cascading list of dependencies for my child classes that include all the dependencies of their supers. This means that a added dependency to the base class needs to be reflected all the way down the line. It is my understanding that ColdSpring deals with this scenario (as well as other entire non-inheritance related duplication of configuration) with the parent attribute in its XML config which allows you to automatically assume the dependencies of another component. I like that to a degree, and I'm sure LighWire could be easily enhanced to do the same, but it still doesn't keep be from having to account for it in my actual components' inits. Discuss.