Dependency Injection With A Framework

posted on 03/29/08 at 09:56:54 pm by Joel Ross

In a recent post, I laid out how I went about adding Dependency Injection to a particular class to give it flexibility and make it easier to maintain over time. At the end, I mentioned I'd never used a DI framework, mainly because I hadn't felt the need to do so. In the comments, Matt Blodgett mentioned that he'd like to see a simple example using a framework, and I agreed.

So last night, that's exactly what I did. I picked Castle Windsor, since that seemed to be a popular one, recommended by Michael Eaton and first on Scott Hanselman's list of DI frameworks.

All in all, it took about 30 minutes to get my existing code base to use it, and that included the time it took me to read the documentation to figure out what I'm doing. I'm obviously not an expert, and there's more I would like to learn, but I did get a basic example working.

For reference, here's our User class that we've been working with:

   1:  public class User
   2:  {
   3:     private IServiceAPI _service;
   4:     public IServiceAPI Service
   5:     {
   6:        get { return _service;}
   7:     }
   8:   
   9:     private string _userName;
  10:     public string UserName
  11:     {
  12:        get { return _userName; }
  13:     }
  14:   
  15:     public User(string userName, IServiceAPI service)
  16:     {
  17:        _userName = userName;
  18:        _service = service;
  19:     }
  20:   
  21:     public void SendMessageTo(string message)
  22:     {
  23:        service.SendMessage(String.Format("@{0} - {1}", this.UserName, message));
  24:     }
  25:  }

I realized after I posted my original post that I never showed what code to use this class would look like, but if you're not using a framework, it might look something like this:

   1:  IServiceAPI service = new TwitterAPI();
   2:  User user = new User("RossCode", service);

That's not really that complicated because we're just using one service, but if you start to get a few different services, you can see how complicated this could get.

To use Windsor, your code to instantiate the User class looks like this:

   1:  IWindsorContainer container = new WindsorContainer(new XmlInterpreter(new ConfigResource("castle")));
   2:  IDictionary parameters = new HashTable();
   3:  parameters.Add("userName", "RossCode");
   4:  User user = container.Resolve<User>(paramters);

We use a dictionary to pass any non-injected parameters - our User class expects a username and an IServcieAPI implementation. We know the username, so we pass that in directly. We let the framework inject the IServiceAPI implementation.

There's actually more code to do it this way, but here's the key - this code doesn't change if I want to use a different IServiceAPI implementation. In the first example, I would have to add or change the code to get another IServiceAPI implementation. With the new way, the code doesn't change - Just the configuration. There's a decoupling advantage that I'll touch on later as well.

I did everything through configuration. It doesn't have to be, but that's how I did it. Let's take a look at the configuration file:

   1:  <configuration>
   2:    <configSections>
   3:      <section 
   4:        name="castle" 
   5:        type="Castle.Windsor.Configuration.AppDomain.CastleSectionHandler, Castle.Windsor" 
   6:      />
   7:    </configSections>
   8:    <castle>
   9:      <components>
  10:        <component 
  11:          id="business.user" 
  12:     type="Business.User, Business" 
  13:        />
  14:        <component 
  15:          id="service.twitter" 
  16:     service="Interfaces.IServiceAPI, Interfaces" 
  17:     type="Twitter.TwitterAPI, Twitter" 
  18:        />
  19:      </components>
  20:    </castle>
  21:  </configuration>

The first just sets up Castle as a valid configuration section. The key parts are in the <castle> section. I'm only using components - there's a lot more I could do here, but I don't need anything more than this. I've registered two components. The first (line 10) is the type I want to inject into, and the second (line 14) is the type I want to inject with. So, Business.User is the class that I want Windsor to construct for me. It'll look for a constructor, which in this case, expects a username, which we supplied, and an IServiceAPI-supported type. The second component has a service attribute, saying that it can be used when you need to find an IServiceAPI type. Then it specifies the concrete type to use - Twitter.TwitterAPI. So when Windsor tries to construct the first component (Business.User), it'll end up resolving the IServiceAPI parameter to Twitter.TwitterAPI. All of this happens with nothing more than some configuration settings!

Now let's say we want to swap out TwitterAPI for our ConsoleAPI class. Before, we'd have to modify the code to create a ConsoleAPI instance and pass it in. Now we just change our configuration to add a new service and specify which service to use:

   1:    <castle>
   2:      <components>
   3:        <component 
   4:          id="business.user" 
   5:         type="Business.User, Business" 
   6:        >
   7:          <parameters>
   8:            <service>${service.console}</service>
   9:         </parameters>
  10:        </component>
  11:        <component 
  12:          id="service.twitter" 
  13:         service="Interfaces.IServiceAPI, Interfaces" 
  14:          type="Twitter.TwitterAPI, Twitter" 
  15:        />
  16:        <component 
  17:          id="service.console" 
  18:          service="Interfaces.IServiceAPI, Interfaces" 
  19:         type="Console.ConsoleAPI, Console" 
  20:        />
  21:      </components>
  22:    </castle>

I've added a second implementation for the IServiceAPI service, and specified which implementation using the <parameters> element. the <service> element is what to pass in as the service argument in the Business.User constructor. In this case, I specified to use Console.ConsoleAPI, which would be another service that implements IServiceAPI. I can easily switch between the TwitterAPI and ConsoleAPI just by changing one config setting - no need to recompile. And I can add new IServiceAPI implementations in the future, add them as components, set it as the one to use for Business.User, and I'm ready to go - no code changes.

That last point brings me to another advantage I see: Decoupling. Before, my client code was tightly coupled to the TwitterAPI implementation. My User class wasn't, but the client using it was. Doing dependency injection by hand removed some coupling, but it couldn't remove as much as using a framework did. In the above scenario, I have five assemblies - Business, Twitter, Console, Interfaces, and Client (the only one not listed, but it would be the client code containing the Windsor code). Client doesn't need to have a reference to Console or Twitter, but can still use the classes inside of them. The assemblies just need to be dropped into the same folder as the executable. And if I ever add a Jaiku assembly, that just needs to be dropped in and configured, and suddenly my code can use it - without compilation.

One other thing to note, and then I'll wrap it up. My User class was unchanged. Some frameworks use attributes to specify where to inject to, but that would require changes to the User class. Windsor doesn't (or at least doesn't have to - I'm still learning!), so I'm free to use the class with a framework or I can do the DI by hand.

Overall, I'm really impressed with Castle Windsor, and I think I can see it's usefulness. To be completely honest, the recompilation (or more specifically, not having to recompile) isn't that big of a deal to me - I rarely am in a situation where I want to add functionality but recompiling the software is out of the question. But breaking a(nother) dependency is always a good thing, and being able to use configuration over code seems like a Good Thing to me.

Bottom line: I think I'll be looking at this a bit for future projects.

Categories: Development, C#

12 comments »


 

12 comments

Comment from: Nathan Bryan [Visitor] Email
Thanks for doing the research for me. I was curious what exactly a DI framework is useful for. It still seems like it's total overkill and unnecessary for *most* projects, but in certain cases it might be extremely useful.

For example, in your case, why not just write an IService factory that returns the correct concrete IService implementation based on some config setting? That's essentially what Windsor is doing here. More typically you only need DI for unit testing, but your client code doesn't particularly need to be decoupled or extensible.

Don't get me wrong, I think I can see the value here. I just think it would be a rare case to really *need* this sort of functionality. I generally prefer to keep code as simple as possible and only introduce complexity when needed. Using a DI framework is probably over engineering for most projects.

Unfortunately (or maybe fortunately) most of my projects are relatively small and have little need for an interesting OO design. It's a shame too, because design patterns are one of my special interests.


03/29/08 @ 23:43
Comment from: Joel Ross [Member] Email · http://www.rosscode.com
Nathan,

That's a good point, and something I've struggled with - is DI over engineering?

For those not familiar with the framework, container.Resolve() might as well be called container.PerformBlackMagic(), because it's not obvious how it's really doing it's work.

I still think there's value in DI for certain cases, but you're right, it might be overkill on smaller projects.

Still, I think it's important to understand both sides of the fence, because you can't make an informed decision if you only know half the story.
03/30/08 @ 14:15
Comment from: Steven Harman [Visitor] Email · http://stevenharman.net
Nathan, One thing to keep in mind is that any code that directly references the Container should be limited to pure "infrastructure code"... meaning you wouldn't normally use it in your business-logic code. The goal there is to keep the code that really provides value infrastructure ignorant.

And as you pointed out, you could do the same thing and still shoot for infrastructure ignorant code by rolling your own factory... but why? That's just more code that you need to maintain and its likely not going to be nearly as powerful as a full-featured container.

For example, one of the nice things about most containers is they will manage the object lifecycle of the objects it creates. Meaning, if you want a singleton object, you don't need to do any Static-fu... just tell the container that MyCoolObject should be a singleton. Or you can also have it cache an object per thread, WebRequest, or build a new one every time one is requested.
03/30/08 @ 21:45
Comment from: Joel Ross [Member] Email · http://www.rosscode.com
Steven,

Thanks for the insights. I hadn't dug deep enough yet to find a few of those things out.

I really could have used the caching per web request a while back. I rolled the whole thing myself - and at one point had a major bug with the storage. It would have been much easier if I could have done it through configuration.

Oh - and I obviously figured out the DI Framework thing myself, so I never ended up posting the blackjack dealer code. If you're still interested in it as an example, let me know.
03/30/08 @ 22:11
Comment from: Steven Harman [Visitor] Email · http://stevenharman.net
Actually, yeah I am interested in using the code for a sample. Tho since you've already added Windsor, maybe I can get one of the other Twitter-bots to go Open Source and then I can add StructureMap or Ninject to it... might be nice to have a couple of them out there for comparison.

http://twitter.com/stevenharman/statuses/779919173
03/30/08 @ 22:34
Comment from: Matt Blodgett [Visitor] Email · http://www.mattblodgett.com
Great post, Joel.

But, I have to agree with Nate. My first reaction was "Whoa! That's a lot of code!"

Not just that, but it's _butt ugly_ code. Maybe I'm being naive, but I can't shake the notion that if the code is ugly, something is wrong.

Do you know what I mean? Is there some some sort of "law" at work where you must sacrifice readability/comprehensibility for flexibility?

(I'm probably totally wrong here, and feel free to brutally mock me if so :-))
03/31/08 @ 20:34
Comment from: Joel Ross [Member] Email · http://www.rosscode.com
Matt,

If you're talking about the configuration files, yes - those are quite ugly. However, in my actual files, I think it's less ugly because there's no line breaks.

It's ugly because it doesn't really make sense - at least that was how I first looked at it. Once I started using it, I figured out what it was doing, and it's not ugly to me any more.

Also, I'm curious to see what an experienced DI framework user does with the container. My guess is it's centrally located and hidden from the rest of the code - a lot like a factory would be. The difference is that for the factory, you maintain the code and for DI frameworks, you maintain the configuration.

Again, I struggle as well. This experiment was about figuring DI frameworks out. I definitely see some value here, and frankly, wiring it up wasn't bad, but I think I'd have to use it on a full-blown project before I could say one is better than the other.

As far as readability / comprehensibility, I've seen some ugly factory code as well, so maybe you do have to sacrifice that for flexibility. Another law for you?
03/31/08 @ 21:13
Comment from: Nate Kohari [Visitor] Email · http://kohari.org/
@Matt:

It's true that it's a lot of code for a simple situation. As it is with any framework, there's a certain amount of boilerplate, and your project has to be of a certain level of complexity to reach the break-even point. (Good frameworks are the ones with low break-even points!) In a full-fledged project, you'll find that you quickly reach the break-even point with a DI framework.
03/31/08 @ 21:28
Comment from: Joel Ross [Member] Email · http://www.rosscode.com
Nate,

Thanks for stopping by. I'm curious though - when you do a real project (not a sample app), how do you handle the container? Is it stashed away some place? or do you instantiate it every time you need it?

I think I know how I'd handle it, but I'm curious how you do it.
03/31/08 @ 21:59
Comment from: Nate Kohari [Visitor] Email · http://kohari.org/
If you're interested in a Ninject implementation of the example, I just published a post about it that I wrote last night: http://kohari.org/2008/04/01/frameworks-and-the-break-even-point/

As far as the container goes, I typically just create it in the Main() method (of a WinForms app) or the Init() method of an ASP.NET app. I'd say you should try to avoid passing it around through your code. While there's nothing wrong with that per se, it can tend to clutter your code a bit. With DI the clutter is at least pushed out of the consuming code!

Sometimes you need it for more advanced scenarios, though... if you expose a property (or constructor argument) that has the type IKernel, Ninject will pass the object a copy of the kernel when it's activated.
04/01/08 @ 09:23
Comment from: Steven Harman [Visitor] Email · http://stevenharman.net
Joel, if you're willing to push the BlackJack dealer out to a public source repository I'd be happy to go over it with you to see if we can tuck the container logic away in the infrastructure code. Like I was saying, a small application like this could serve as a great example for those just digging into concepts like DI, SoC, etc...

So... what do you think?
04/03/08 @ 20:36
Comment from: Joel Ross [Member] Email · http://www.rosscode.com
Ok. I'll do it. But be gentle when you look at it!

I have a few days off, so I'll clean it up a bit and get it posted out there somewhere.
04/04/08 @ 08:45

Leave a comment


Your email address will not be revealed on this site.

Your URL will be displayed.
(Line breaks become <br />)
(Name, email & website)
(Allow users to contact you through a message form (your email will not be revealed.)