Dependency Injection doesn't cut it anymore
So, I showed how you can write an IoC container in 15 lines of code, obviously this means that containers such as Windsor, at ~52,000 lines of code are tremendously bloated and not worth using, right?
Well, no. And the reason is that DI is now just one of the core functions of a container, it is the other things that it does that make it so useful. Since the discussion started from why you don't need IoC in Ruby, I decided to also explore the ideas in the Ruby frameworks. It seems like a typical Ruby IoC code is this:
registry.register( :component ) do |reg| c = Component.new c.service = reg.some_other_service c end
This is very similar to the idea of the demo container that I build. Basically, it is using the block (anonymous delegate) to defer the creation to some other time. On the surface, it is a viable approach, but it breaks down as soon as you start dealing with even moderately complex applications.
I will get back to it in a minute, but let us walk through the core things that an IoC container should provide.
- Dependency Injection
- No required dependency on the container from the services
- Life style management
- Aspect Oriented Programming
The above is what I consider the core minimum to be an IoC container. It is not really that hard to build, and using Dynamic Proxy I can probably get this all working in about three hours. But it wouldn't be very usable. (And yes, it will have facilities :-) )
The reason that I use IoC is not to encourage testing, it is not to break dependencies, it is not to get separation of concerns. Those are well and good, but if I had to use a container that behaved like the one listed above, I would go crazy.
I am using IoC because it makes all of the above so easy. It make it harder to create a manual dependency than to do the wiring through Windsor. As a matter of fact, I went crazy about a year ago just having to deal with just specifying service declarations. That is why I have Binsor in place. That took care of all the complexity.
Now testability, separation of concerns and no dependencies are no longer things that I have to work toward, they are already there, right out of the box.
My current project has well over 250 components that are managed through Windsor. I have no clue as to their dependencies, and I don't really care about that. Other people on my team keep adding components, and they aren't even aware that they have IoC there. They just know that if they put the IFooService in the ctor, they will get it when the code is running. And if they add IBarService and BarServiceImpl, they will be available to any component that uses them.
Can you imagine trying to deal with 250+ dependencies resolving blocks (or anonymous delegates) ? Just writing them down would be a chore, and then they would have to be maintained, and adding something is becoming fragile. I can't imagine having something break because I added a parameter to the constructor, or a settable property that I need. I can't imagine having to do something beyond just creating a class to get it to register in the container.
Well, I can imagine those, I can't imagine me suffering through that. It would be like trying to pull a particular fish from the image on the right, possible, but very painful.
Auto wiring is what makes IoC a mandatory part of my application, because it means that I don't need to manage the dependency cloud at all. I let the container do it for me. It means that once is has been setup, you no longer need to think or be aware of it. I have forgotten that I am using an IoC container for three months, while using it each and every day.
Then you get to the AoP concepts, of which I particularly like automatic transaction support and cross thread synchronization.
To conclude, I think that if you are using an IoC merely for dependency injection, you are missing a lot of the benefits there. Having the ability to just throw the complexity at a tool and have it manage it for you is a key factory in how you design your applications.