By Peter Bell

Constructor vs. Setter Injection

I think there are some good arguments for constructor injection in terms of a well-defined contract for what a particular object requires. However I believe it is impossible (?) to resolve circular dependencies using constructor injection and as I found out the other day, all of the applications I build have at least the potential for circular dependencies. Why then bother to go to the added complexity of supporting constructor injection if you’re not going to use it all of the time? After all, what could be worse than documentation that appeared to be authoritative but that was actually not complete?!

For now LightWire is setter injection only using a single base AddObject() method. There are lots of reasons not to go this way if you want your code to be self documenting, but if you see as documentation as a separate output from a core metadata repository rather than something you deduce from your cfc’s then I’m not seeing much in terms of a downside.

Comments
Peter,

Constructor injection has other benefits, like potentially more testable code as well... because the CFC is "ready to go" at instantiation (because you required your dependencies to be injected via the "constructor") you know that once it's instantiated it's usable without having to call any other setters.

I know there's some debate, but I personally prefer to use constructor injection... if for no other reason that having to code up createObject().init() and then object.setFoo(foo) and object.setBar(bar) seems to be a waste of code. Then again, I'm finnicky that way. ;)

As for solving the circular reference issues with constructor injection, there are obviously times when property injection is going to be more useful... or you can make your constructor arguments optional on certain types of objects so you can instantiate one without the dependency and solve the references issue after the fact... which then circles back to the idea of using property injectors.

I haven't run into this issue yet because I don't really have anything that uses circular references yet... maybe someday I'll need to, but not at the moment.

Laterz,
J
# Posted By Jared Rypka-Hauer | 10/8/06 6:48 PM
Hi Jared,

Firstly I should clarify how I do setter injection (yeah, I know, if I'd published the code already you'd KNOW this!!!).

When you ask LightWire for an object, if it is a singleton, it looks to see if it has already been created (if it is a transient, it skips this step). If so, it just gives you the object. If not, it runs a routine that resolves all dependencies n-levels down (all of the dependencies of the dependencies of the dependencies - unlimited depth) while handling circular dependencies without getting stuck in a loop. It then creates ALL of the objects you need (the objects that your objects need, the objects that those objects need, and so on). It then creates the object you asked for. It then setter injects all of the dependencies into all of the objects you need and into the object you requested. It then gives you the object you asked for.

So, in practice while I am using setter injection, there is a single method call that does everything and the object you get back is fully loaded with dependencies, it is just that the problem is easier to solve this way and it includes the ability to handle circular dependencies which are actually pretty common.
# Posted By Peter Bell | 10/8/06 7:02 PM
Only thing I'm thinking of right now is that this probably won't be thread safe (haven't tested it yet) in which case I'll just need to put some kind of transaction lock into LightWire using a variables scope and maybe even adding IsLocked and Lock methods to the public methods. If this becomes an issue it should be a fairly easy one to fix . . .
# Posted By Peter Bell | 10/8/06 7:04 PM
Jared pretty much nailed it but perhaps didn't outline it quite clearly enough:

Constructor injection lets you take existing code that knows nothing about dependency injection or ColdSpring or anything and have ColdSpring (or Lightwire?) use it as-is with no code changes.

Just to reiterate, I have existing code that works like this:

myService = createObject("component","path.to.my.Service").init(some,list,of,args);

At any point, I can take Service.cfc and have it managed by ColdSpring - with no changes to Service.cfc - and reuse it in another piece of code. And we can easily use ColdSpring in our original application too:

myService = application.cs.getBean("myService");

or even just:

function setMyService(svc) { variables.myService = arguments.svc; }

and have CS inject it.

No change to Service.cfc. It doesn't know. It doesn't care.

If you don't have constructor injection, you have to change any CFCs that expect init() to be called immediately on creation.
# Posted By Sean Corfield | 10/23/06 11:33 PM
Hi Sean,

Yep, this is why I quite like constructor injection and that is what LightWire does now. You just need to have a bunch of cfarguments in your init() within service and (as you mentioned) you can take a cfc and move it in or out of a DI framework and it doesn't break the cfc either way.

The only point I'd make is that because (thankfully) there aren't constructors in CF (only the init() CONVENTION) you can actually resolve circular constructor dependencies as I do in LightWire which would be impossible in Java.

For example, if you're lazy loading, you just create first object without calling init and then create all the objects it depends on n-levels down without calling their inits. Then you call the init methods on all of them so it is some kind of mix of constructor and setter injeciton in that you get the benefit of resolving circular dependencies (which is impossible in Java constructor injection) but you still have all of the arguments in the init() method so you could remove your DI framework and replace it with manual passing of the dependent objects without making any change to the cfcs you're creating. Best of both worlds :->

This is also why I'm praying CF doesn't get constructors as if people started using them we'd have to genuinely choose between the reusability and documentation benefits of constructor injection and the fact that you can't resolve circular dependencies (which are quite common) without using setter injection.
# Posted By Peter Bell | 10/23/06 11:56 PM
Should clarify, the whole "create n objects without calling init and then call all of their init methods) is an atomic transaction - you don't have the uninit'd objects floating round - LightWire does all of this in one go and then returns the fully loaded object requested.

Obviously some potential thread safety issues I'll need to think through as atomic NEQ instantaneous!
# Posted By Peter Bell | 10/23/06 11:59 PM
You're missing the point... but I know you'll get there soon... :)

How can you call the init() methods in the correct order? Constructor injection means that there is a dependency chain between constructors (init() methods).
# Posted By Sean Corfield | 10/24/06 12:56 AM
I may still be missing the point, so help me with this.

I want to get ProductService. ProductService depends on ProductDAO and UserService. UserService depends on ProductService and UserDAO. ProductDAO and UserDAO have no dependencies and we're lazy loading.

I create ProductService (without calling init, so it is unitialized - tacky, but bear with me). I then create all other dependent objects, n-levels down (ProductDAO, UserService and UserDAO) - again without calling their inits.

I then call their inits in any order. For instance, I call ProductService.Init() and give it the reference to the uninitialized UserService and ProductDAO. I then call ProductDAO.init() to initialize it (so now the ProductDAO referenced by ProductService is initialized). I then call UserService.init() passing it a reference to the initialized ProductService and the uninitialized UserDAO. I then call UserDAO.init(), initializing the DAO that was referenced by the UserService.

I do all of this as a "single operation", returning ProductService which is now initialized and fully loaded with initialized objects. Of course, while LightWire was doing all of this I passed in references to objects that hadn't been initialized, but as I wasn't asking those objects to do anything (I was just providing references), who cares?! By the time I returned the ProductService, it and all of its dependent objects were fully initialized, so I get the benefit of resolving circular dependencies AND using "constructor" based injection (clearly it ISN'T constructor based as I'm not using the inits as true constructors - but it LOOKS like constructor based init).

Make sense?
# Posted By Peter Bell | 10/24/06 1:57 AM
Interesting idea, though...

Two objects... each dependent on the other. Each is supposed to be an argument to the other.

CF cares about the type, not whether it's been completely "instantiated" or not... so if you create the object without calling init(), you can pass it into the second object's init() method. Then you can call init() on the first object, passing the second object into it. Since CFCs are passed around by reference, both objects will then be subsequently completely init()'ed.

It's an interesting idea... I can't really see any reason why it wouldn't work.

Also, I can't believe I didn't think of it sooner. It's so obvious, there's GOT to be a reason it won't work... ;)

J
# Posted By Jared Rypka-Hauer | 10/24/06 1:58 AM
There may well be, but I have it working and it seems to be doing fine - I finally posted the LighrtWire code on RIAForge.com and the whole thing including lazy load, "constructor" injection and resolving circular dependencies for constructor injection in under 200 lines of code so it isn't very hard to check it out or trick it out to work differently.

Who knows, maybe one of my dumb questions will have ended up with an interesting answer?!!!
# Posted By Peter Bell | 10/24/06 2:03 AM
But what if your init() method does someObject.getSomeValue() ? This is why IN GENERAL you cannot just randomly "construct" (call the init() method) on objects. init() = construction = an object is fully initialized and ready for use = you can call methods on it.

Make sense?
# Posted By Sean Corfield | 10/24/06 2:14 AM
Yep, that'd fail. I might be willing to live with that as a "cost of doing business" . . .

Just to get an idea, how often do you find yourself calling methods on an injected object as part of your init methods? Is this a theoretical, a seldom but essential, a seldom but could work around or an everyday occurance?
# Posted By Peter Bell | 10/24/06 2:20 AM
In init() methods the assumption is that objects are completely initialized. In other words, yes, it's common in large systems to assume you can call arbitrary methods on an object passed into a constructor.
# Posted By Sean Corfield | 10/24/06 2:24 AM
Yep... there ya go.

*sigh*

So much for simple solutions. :) Now that I think of it, I've had this conversation before...

If it really were that simple, it wouldn't have been so hard for the CS folks to take care of the circular references issues they had to deal with.
# Posted By Jared Rypka-Hauer | 10/24/06 3:08 AM
oh well!
# Posted By Peter Bell | 10/24/06 7:31 AM
You know,

On reflection I'm just going to "YAGNI" this for now and leave the engine as it is - just documenting the limitation. I haven't yet come across a use case where I need to call a method on one of the dependent objects within the init method of another.

When/if I do, I'll just have to go back to requiring setter injection for circular dependencies and will have to spend an afternoon getting my head around the constructor dependency resolution order. If nothing else it'll be a cool algorithm to write!

Thanks for ALL the help guys - much appreciated. Between you, you'll make a programmer out of me one of these days :->
# Posted By Peter Bell | 10/24/06 7:54 AM
Given that this posting has made it into my top tex most viewed ever (who knows how), thought I better update this. LightWire now uses proper constructor injection while also supporting setter injection and mixin injection (same as setter but doesn't require all the ugly setxxx() methods - doesn't support auto-wiring, but I don't think autowiring is a good thing so I can live with that).

In the end resolving circular dependencies was fairly trivial and all code including dependency resolution and handling comes in at under 300 lines (which is consistent with what I heard someone else suggest a DI engine should require in a dynamically typed language - I want to say it was Bruce Eckel or Martin Fowler, but can't remember for sure).

Still need to add support for reading CS config files, basic AOP, and there are a bunch of Spring 2.0 features I'm also looking to add. For anyone interested, code is at lightwire.riaforge.org.
# Posted By Peter Bell | 1/22/07 6:02 PM
BlogCFC was created by Raymond Camden. This blog is running version 5.005.