Posted on 3/23/2009 12:19:52 AM by Justin Etheredge
I was speaking with Phil Haack this week at MIX 09 and we were discussing his choice to drop Dependency Injection from his talk on ASP.NET MVC Ninjas on Fire Black Belt Tips. Which, by the way, was an awesome talk that you should go check out if you weren’t able to be there. The reason that Phil chose to remove the Dependency Injection piece of his talk was simply because whenever you show off dependency injection in a very simple scenario it always seems to devolve into a “why would I want to do it that way?” sort of discussion. Phil suggested that some thought needed to be put into a scenario complex enough to show the benefits of Dependency Injection, yet simple enough to still get across to an audience. (I’m going to be using Ninject 2 in this post, which is currently in beta. Go check it out, and be sure to thank Nate for writing it.)
The Problem
The problem is that the majority of the benefits of dependency injection only become apparent when you start dealing with scale. The entire concept of "Inversion of Control" (which is the underlying principle on which Dependency Injection operates) lends itself to larger and more complex applications. "Inversion of Control" is all about externalizing the flow of the application. In its Dependency Injection incarnation, instead of instantiating classes which will then in turn do work to go out and find their dependencies, we are asking the container to construct a class for us, while at the same time finding and resolving all dependencies and passing those to the class. We don't have to think about how this object is constructed, the Dependency Injection container "wires it up" for us. Because of this, it really only lends itself to larger applications because externalizing the flow of an application causes overhead. It is no longer possible by looking at the source of the classes to tell what dependencies will be used at runtime. In a large system this is much less of a concern, because we are now moving massive amounts of repetitive configuration out of classes and into more centralized locations where they can be changed more easily. If you want to read more about Inversion of Control, you should go over to Martin Fowler's Bliki.
To enforce the idea that Dependency Injection is too complicated for simple setups, let’s create a very simple scenario using a service class and a repository that many of us are probably look at fairly regularly. First we will have a repository:
public class UserRepository: IUserRepository
{
public IEnumerable<User> GetUsers()
{
return new List<User>();
}
}
And an interface for that repository:
public interface IUserRepository
{
IEnumerable<User> GetUsers();
}
And then we have a service class takes the repository as a constructor:
public class LoginService
{
private readonly IUserRepository userRepository;
public LoginService(IUserRepository userRepository)
{
this.userRepository = userRepository;
}
public void LoginUser(string username, string password)
{
//some crap that uses repository to get user and check password
}
}
Then I declare an interface for this:
public interface ILoginService
{
void LoginUser(string username, string password);
}
Then we config the dependency injection:
public class DefaultModule: Module
{
public override void Load()
{
Bind<ILoginService>().To<LoginService>();
Bind<IUserRepository>().To<UserRepository>();
}
}
Then I request my LoginService:
var service = kernel.Get<ILoginService>();
And then I pat myself on the back for doing such an awesome job of showing everyone how sweet dependency injection is, while simultaneously mocking everyone who just “doesn’t get it”. It is pretty awesome, right?
Only the above looks good to masochists only. If you even have the slightest of pragmatic blood in you, the above looks like ridiculous overkill. And for an application that simple, it is extreme overkill.
The Realization
In fact, using “poor man’s Dependency Injection” we could just have easily done it like this:
public class UserRepository
{
public IEnumerable<User> GetUsers()
{
return new List<User>();
}
}
And the service like this:
public class LoginService
{
private readonly UserRepository userRepository;
private LoginService(UserRepository userRepository)
{
this.userRepository = userRepository;
}
public static LoginService Create()
{
return new LoginService(new UserRepository());
}
public static LoginService Create(UserRepository repository)
{
return new LoginService(repository);
}
public void LoginUser(string username, string password)
{
//some crap that uses repository to get user and check password
}
}
And been done with it. I even created factory methods on the service to show that we can still have the abstraction away from the constructors. So I could get my LoginService like this:
var service = LoginService.Create();
And what do we lose here? Well, I got rid of the interfaces, but we could have kept those. So we didn’t really loose anything in the abstraction front. We also didn’t lose anything in the flexibility front, because I abstracted away the constructor using a factory method, so if we need to change how they are instantiated, we still have that option. And we also didn’t lose anything in the clarity front, in fact, I think we gained big here. It is much easier to tell how the application is going to behave.
At this point you may be thinking that I am an opponent of dependency injection, but the reality is that you couldn’t be more wrong. I am a huge proponent of it, and I tend to use it even on fairly simple projects. But from what we have seen above, it may be hard for someone to wrap their head around exactly what benefit DI is going to give them.
Benefits, We Are Going To Get There
So, let me say one thing before we really start to find a way to show off Dependency Injection. In order to ever appreciate DI you really have to subscribe to the single responsibility principle (often referred to as SRP). If you don’t see the benefit of each class being responsible for one thing, and you are a fan of 5000 line classes, then I’m not sure that I am going to be able to help you. You really have to love composition of classes, but you may be someone who simply doesn’t mind constructing dependencies inside of the enclosing classes or simply uses the service locator pattern to pull in dependencies.
When I started thinking about this problem, I thought “great”, I’m going to have to come up with some stupidly complicated example that really will end up doing more harm than good. But the more I thought about the problem, the more I realized that classes pulling in their own dependencies doesn’t get complicated very quickly, but when classes don’t contain enough context to configure themselves, then config can start to get ugly pretty quick.
So let’s get back to our earlier example of the UserService and UserRepository class. If you are familiar with the Repository pattern then you are probably familiar with the idea that its main purpose is to abstract an underlying persistence store so that everything looks like an in-memory list. This extremely simple abstraction works amazingly well, even if enterprise situations where we have multiple back-end stores. So let’s go ahead and pretend that I work in Acme Insurance and I have two different web services that I am going to be hitting against in order to pull my user data. We will use a standard IUserWebService contract for these web services. We are going to need to get those two dependencies into our UserRepository so that we can use them as stores for user data. These guys will look like variations on this:
public interface IUserWebService
{
IEnumerable<User> GetUsers();
}
And then in a real application, we would have the automatically generated WCF proxy classes for these, but in this case we are just going to create a few dummy stub classes. Okay, so we have these new services, and they are going to have to get into the repository, but we are still doing good with “poor man’s” Dependency Injection. All we need to do now is to just instantiate these services inside of the repository like this:
public UserRepository()
{
userWebServices = new IUserWebService[2];
userWebServices[0] = new UserWebServiceFacade();
userWebServices[1] = new UserWebServiceFacade();
}
Easy enough. We can also expose a constructor that will allow us to pass these are parameters too for testing:
public UserRepository(params IUserWebServiceFacade[] userWebServices)
{
this.userWebServices = userWebServices;
}
The only problem is that now how are we going to tell the two services in the constructor up above their config information? You see what I am talking about? These two lines:
userWebServices[0] = new UserWebServiceFacade();
userWebServices[1] = new UserWebServiceFacade();
You see we haven’t really gotten all that complicated, but already we have hit a point where we have dependencies that don’t contain enough information to configure themselves. So what do we do? Well, we could do this:
public UserRepository()
{
userWebServices = new IUserWebServiceFacade[2];
userWebServices[0] = new UserWebServiceFacade("http://www.test.com");
userWebServices[1] = new UserWebServiceFacade("http://www.test2.com");
}
But we don’t really want to hard code the configuration for these straight into the repository, so we can do this instead:
public UserRepository()
{
userWebServices = new IUserWebServiceFacade[2];
userWebServices[0] = new UserWebServiceFacade(ConfigurationManager.AppSettings["service1"]);
userWebServices[1] = new UserWebServiceFacade(ConfigurationManager.AppSettings["service2"]);
}
But now we have code which directly accesses configuration and then passes it to a child class. Okay, so you don’t really care about that…but what happens now when we need to do some decryption on data coming back from these services? So we throw an IDecryptor into the mix (surely you don’t think that the repository should be decrypting data, do you?):
public interface IDecryptor
{
string Decrypt(string encryptedString);
}
Hmmm. The issue becomes now that both of these need a different key, you don’t expect disparate business locations to use the same encryption key do you? So if we need to get keys and more config, then now the repository constructor will start looking like this:
public UserRepository()
{
userWebServices = new IUserWebServiceFacade[2];
IDecryptor decryptor1 = new Decryptor(ConfigurationManager.AppSettings["key1"]);
IDecryptor decryptor2 = new Decryptor(ConfigurationManager.AppSettings["key2"]);
userWebServices[0] = new UserWebServiceFacade(ConfigurationManager.AppSettings["service1"], decryptor1);
userWebServices[1] = new UserWebServiceFacade(ConfigurationManager.AppSettings["service2"], decryptor2);
}
Wow. This is starting to get a bit ridiculous. Next thing you know they are going to use different encryption schemes, ports, etc… Once you start thinking about other config settings, we either have to start reading them in from config right in the constructor, or pass them in.
If we were to use DI then we would have this config (in the latest Ninject 2 branch):
Bind<ILoginService>().To<LoginService>();
Bind<IUserRepository>().To<UserRepository>();
Bind<IUserWebServiceFacade>().To<UserWebServiceFacade>()
.Named("Service1")
.WithConstructorArgument("url", ConfigurationManager.AppSettings["service1"]);
Bind<IUserWebServiceFacade>().To<UserWebServiceFacade>()
.Named("Service2")
.WithConstructorArgument("url", ConfigurationManager.AppSettings["service2"]);
Bind<IDecryptor>().To<Decryptor>()
.WhenParentNamed("Service1")
.WithConstructorArgument("key", ConfigurationManager.AppSettings["key1"]);
Bind<IDecryptor>().To<Decryptor>()
.WhenParentNamed("Service2")
.WithConstructorArgument("key", ConfigurationManager.AppSettings["key2"]);
As you can see the ILoginService is tied to LoginService, IUserRepository is tied to UserRepository, and then we bind IUserWebServiceFacade to UserWebServiceFacade twice with two different service addresses. Then we use these named bindings to bind the two classes used to decrypt the data.
So we end up with a bunch of classes with simple constructors, only referenced interfaces, which can be built and configured in virtually any way by the Dependency Injection container. The configuration may look complicated, but when you consider that for most interfaces you will have only a single implementation, the configuration stays much more simple than you would imagine.
The Repository:
public class UserRepository: IUserRepository
{
private readonly IUserWebServiceFacade[] userWebServices;
public UserRepository(params IUserWebServiceFacade[] userWebServices)
{
this.userWebServices = userWebServices;
}
public IEnumerable<User> GetUsers()
{
var result = new List<User>();
foreach (IUserWebServiceFacade service in userWebServices)
{
result.AddRange(service.GetUsers());
}
return result;
}
}
The Web Service:
public class UserWebServiceFacade: IUserWebServiceFacade
{
private readonly string url;
private readonly IDecryptor decryptor;
public UserWebServiceFacade(string url, IDecryptor decryptor)
{
this.url = url;
this.decryptor = decryptor;
}
public IEnumerable<User> GetUsers()
{
// implementation
return new List<User>();
}
}
And the decryptor:
public class Decryptor: IDecryptor
{
private readonly string key;
public Decryptor(string key)
{
this.key = key;
}
public string Decrypt(string encryptedString)
{
// implementation
return string.Empty;
}
}
Reflecting On What We Have Accomplished
We don’t have any knowledge of how these dependencies are constructed anywhere except for the config in our DI container. We can change them from transient to per request to singleton with a simple change. We can replace our web service implementation without digging into the source. What if this web service had many methods which were being used by a multitude of different classes? How many constructors would we wade through in order to update the references to this class in order to change the way it is being built?
These are the kinds of questions that you need to be asking yourself as you look at these examples because even though I think that this example shows off Dependency Injection pretty well, it is still fairly canned. It is hard to truly grasp the full benefits until you have a class which is used across fifty other classes that you suddenly need to modify the constructor on. Now sure you can get the same effect from factory methods, but again the benefits come in scale. If we use service locator, then we need to have that service locator everywhere in order to request our dependencies. Nothing is more satisfying with DI than when you need to add a dependency to your constructor that has already been wired up, then all you have to do is request the type and the rest is just handled for you.
Another advantage of the DI framework is that most of them are setup with lifetime management. This allows you to change a class from per-request to transient to singleton by simply changing a setting. Most DI frameworks will also allow you to create your own reusable custom lifetimes.
Try It For Yourself
As much as I can try to convince you that Dependency Injection is the best thing since sliced bread, you really have to try it our for yourself. After you use it for a bit, you’ll start to realize how natural it is to just throw dependencies into your constructor so that you can keep small classes, but still have them all wired up quite easily. You’ll soon start to realize that it is changing the way you write your code.
Then again, you may try out Dependency Injection and find that you hate it. I urge you to press forward with it, at least for a bit, but if you really find that you hate it then I’d love to hear why. I’d also love to hear from people who love dependency injection, but think that I gave a horrible example.
Summary
In this post we looked at a common problem in many business apps and how we would solve the problem with and without dependency injection. Dependency Injection allows us to wire up our applications in a central place all while using less code. It helps keep our classes loosely coupled and testable, while providing a high level of extensibility. I hope you have found this post useful!