I read a good post by Joel Ross the other day. In the post, Joel implements a simple project using Castle Windsor to drive dependency injection. In one of the comments, Matt Blodgett says that it seems like it takes a lot of code to implement, and that the code isn’t pretty. Naturally, I figured I’d use this an opportunity to point out the advantages of Ninject’s fluent interface vs. XML configuration. :)
The way I see it, every framework library has a cost that you have to pay up-front in order to use the library in your code. This cost might be in a learning curve, a performance penalty, or the fact that you have to write some boilerplate code to make the library work its magic. Typically, the argument is that by paying this up-front cost, you get a benefit that’s significantly larger. This creates a break-even point, where you make up for the “debt” you paid to use the library, and start becoming more effective because of it. The best libraries are the ones that keep the up-front cost as low as possible, so you reach your break-even point faster. Libraries that accomplish this goal can be used in significantly smaller projects, because it becomes easier to justify the up-front cost if you know that you’re going to quickly begin to see benefits in terms of efficiency.
Ninject was written to support enterprise applications, but aims to treat small projects as first-class citizens. To that end, I’ve gone to great pains to try to keep things simple in simple situations, and mask any complexity required for advanced scenarios from everyday use. Here’s the same example that Joel presented, written with Ninject.
First, the User class:
public class User {
public IServiceApi Service { get; private set; }
public string UserName { get; private set; }
[Inject]
public User(string userName, IServiceApi service) {
UserName = userName;
Service = service;
}
public void SendMessageTo(string message) {
Service.SendMessage(String.Format("@{0} - {1}", UserName, message));
}
}
One caveat: I typically try not to combine service logic with my domain model, and so I wouldn’t put the IServiceApi dependency directly in the User class. Not that there’s anything wrong with this, but it means that you have to activate every User that you load in your application via the DI framework, and that can interfere with scalability. It also makes activating the User a little bit more difficult, as you’ll see below.
Next, rather than creating the XML configuration to wire up the components, we create a module, and bind the IServiceApi interface to the TwitterApi implementation:
public class TwitterModule : StandardModule {
public override void Load() {
Bind<IServiceApi>().To<TwitterApi>();
}
}
(You could also add a self-binding for User, but you don’t have to since it’s a concrete type.) Maybe it’s just me, but those 4 lines of code (complete with IntelliSense!) are much simpler than the 20 or so lines of XML. You’re using a big powerful IDE, so why not take advantage of it to set up your DI framework?
Lastly, we need to load the module into a kernel and activate our User. Because of our userName parameter, we need to do a “hybrid” activation by passing a transient parameter to the Get() method. (This is what I meant about separating the domain model from the service model. It lets you avoid things like this.)
public static class Program {
public static void Main() {
IKernel kernel = new StandardKernel(new TwitterModule());
User user = kernel.Get<User>(
With.Parameters.ConstructorArgument("userName", "RossCode")
);
user.SendMessageTo("test");
}
}
You could also just make the User’s UserName property writable, and set it after activating the object.
Now, this is a pretty simple example, and I’d say we still haven’t reached the break-even point with Ninject. However, if we had a couple more services like IServiceApi, each implementation of which had its own dependencies, the power of DI would start to make itself apparent. For example, consider the following scenario:
1. Our TwitterApi depends on a TwitterWebService.
2. We add a JabberApi which depends on a JabberClient.
3. We add a SmtpApi which depends on a SmtpClient, which depends on an EmailMessageFactory.
Assuming JabberClient, SmtpClient, and EmailMessageFactory are all concrete types, your existing application could support all of these new features without adding anything new to your type bindings. (This is because Ninject will automatically create “self-binding” when it encounters concrete dependencies.) You could then easily flip between the different options by altering the binding between IServiceApi and one of your *Api classes. Also, since your module is executable code like anything else, you could make it more intelligent, reading configuration files, command line parameters, or even a database to determine which messaging service implementation should be used.
The flexibility that inversion of control brings to your application benefits you the first time you need to add additional features, or make changes on the fly. Sometimes it’s difficult to understand what the benefit is by reading example code, and it takes some time to get used to writing your code to work with DI. It’s well worth the up-front cost though!
Related posts:











Email
Twitter
LinkedIn
Facebook
{ 11 comments… read them below or add one }
I started using ninject precisely because it lacks xml config. Binsor is also an option, but I thought that ninject overall was quite simple to use. Now, we just need a way to define a standard kernel and module so that ninject can have a static “Get” method so that you don’t need this line “IKernel kernel = new StandardKernel(new TwitterModule());” every time you want to activate something. Then the debt would be even lower.
You don’t need to (and in fact you shouldn’t!) create a new kernel each time you activate something. Most applications only ever need one kernel, typically created during startup.
I actually have an idea in the works for automatic module installation. Basically, it would search through all loaded assemblies for types that inherit from IModule and install them into a kernel.
I’ve also been tempted to create a static facade that would provide easier access to a kernel instance. I’ve avoided it because the purist in me thinks it’s a bad way to go about DI — since it couples all of your code to the static facade. I’ll probably add it before 1.0, but with a warning in the documentation that There Be Dragons. :)
I read your post three times before I realized it was April Fools Day. Good one! I completely fell for it.
@Issac: I take it you don’t agree. :)
Good one. You had me fooled captain refactor.
@John: For once you post a comment with your real name! What’s wrong, run out of other people to impersonate? *grin*
Guess again.
Keep up the good work.
Great post, Nate.
“Maybe it’s just me, but those 4 lines of code (complete with IntelliSense!) are much simpler than the 20 or so lines of XML. You’re using a big powerful IDE, so why not take advantage of it to set up your DI framework?”
Yes! Yes! Yes! I couldn’t agree more.
I’m sorry, I’m new to DI. Can you please explain to me which part of this post is the “joke” and which part is true?
None of the post was a joke. The jokesters just came to fill out the comments. :)
Hi!
By the way, where is the “With.Parameters.ConstructorArgument” in ninject 2.0 ?