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.
Comments
I can confirm every single word! Coming from the "dark" side (i.e. no IoC container) through the jungle (i.e. CAB) I now am using Windsor for my projects and really do NOT want to miss it any more. Especially because it's a light weight container (in contradiction to CAB) and it's so easily extendable (facilities, etc.) I swear like it very very much.
Regarding AOP I'm not there yet. Oren, could you be more specific on that, that is give some working examples as you always do with other stuff (e.g. NHQG). I know the theory of AOP very well... but I feel I could need some more practice...
Keep on writing!
Just a footnote - having delegate-based component creation doesn't preclude a container from also providing reflection-based creation. In most instances using the reflection-driven model is most convenient.
E.g. if all of your controller objects bar one require the same transaction provider, register them all through reflection then override the special case using:
container.Register<OddController>(() => new Controller(oddTransactionProvider));
You get all the features of your programming language at your disposal, and can avoid questions like "how do I specify a constant DateTime in a config file?" or backwards-looking tricks like bijection.
(I've been working on a container like this for C# - while it has miles and miles to go before being a tool for everyone, I'm convinced that there is more fertile ground to explore here.)
AOP Samples run from the common [Transaction], to logging method calls to [UserInterfaceMethod], which move the call to the UI thread.
Nick,
I would rather do it something like:
IoC.OddController.TheDateValue = date.Parse("15/10/2007");
The problem with handling the special cases via code is that it simply does not scale when you have many services.
Well... this'll be a one-sided dialog until you attract someone who's done some serious work with Ruby in non-trivial apps...
Hi Ayende,
The problem for me with setting the value through the instance is that it will have to be resolved first, which is fine in the example we're talking about but might not always be. There is definitely more than one way to skin the cat though.
What do you think is the scalability problem? The fact that you need to reference a lot of assemblies from a central place? I agree with that entirely, but I'm seriously wooed by the Binsor concept, which can handle that problem dynamically.
If spaghetti is a concern, well - I think that can happen whether working in C#, Boo or XML ;)
Would love to hear more of your thoughts on this, I think putting dynamic configuration/extensibility back inside the realm of the primary programming language is going to be a continuing theme in .NET 3.5+ and I can't wait to see where IoC configuration in particular goes.
Cheers,
Nick
Not assemblies, code. The need to define a delegate per component, and maintain it.
As I said, I have about 250 components in my app now, I would be surprise to see it crossing to several thousands.
But for special cases, you've still got to handle them somewhere...
Don't get me wrong though, I still think "foreach (Type component in ...)" is the best thing since sliced bread, and I use it for 95% of the classes I register.
It is the facilities that really bring the value to the table for me as well.
Eventwiring, Startable, Interceptors, etc...
If there is any growth for Castle Windsor for me it would be to include more facilities! (I'd still like to see a good ADO.net facility!)
I've used the facilities in some applications, and it adds tremendous value!
Please dig deeper into the feature benefits, real-world pain, you are writing some great posts and for the first time I just might give Windsor a try.
Thanks…
Can you provide some detail on how the magical automatic registration works?
I've got a very basic IoC container that I wrote (long before I knew of the existence of Windsor) and I use reflection and a custom attribute to automatically register my dependencies. does Windsor do something similar? or something a bit more "magic"?
Comment preview