Most of the classes depend on some other classes to do some work for them. Since those other classes can have their own dependencies, the total number of dependencies (both direct and indirect) of a class can grow exponentially, which means our dependency graph can easily become quite large. In scenarios where dependency graphs are large, resolving dependencies can become a significant task and you should carefully decide:

  • Should all the dependencies be resolved the same way?
  • Should all the dependencies be resolved by the same party?

I wrote this post because I am seeing more and more projects in which all the dependencies for every class are injected through the class's constructor. Developers blindly apply dependency injection for every dependency saying it is the "best practice".

Inject all the things meme

Dependency injection is a best practice in some cases. The purpose of this post is to help you not to abuse it.

Keep in mind that all the dependencies your class' constructor accepts must be resolved by the user of the class (e.g. some other developer), which means that in order for someone to reuse your one class, they have to resolve X dependencies, and since dependency count grows exponentially, X can be pretty large in some cases. Also keep in mind that the user of your class might not have the access to your dependency resolver (e.g. it has it's own process and entry point), so they will have to resolve the whole dependenciy graph by themselves, which can be challenging and affect the reusability of the class.

If you have a class that accepts all its dependencies through the constructor, all those dependencies must have the same level of accessibility as the constructor. So, if it is a public class with a public constructor, which is intended to be used by some external modules, all its the dependencies will have to be public. As a result, developers that are using that class will be able to access and depend on all the dependencies, even if some of them are just implementation details and should be internal. Moreover, since those developers are invoking the constructor of the class constructor, they will have to resolve and provide all the dependencies. This means that other developers will not only be able to depend on all the dependencies, they will HAVE TO depend on them, because it is their responsibility to resolve them and pass them to the constructor. This can drastically affect the encapsulation of the class. Those developers can also decide not to resolve to implementations from our module, but to implement their own. This way they can customize our class to fit their needs. However, since we are accepting all the dependencies through the constructor, including the ones that are just implementation details, they can inject badly implemented dependencies and make our class misbehave.

So, if you inject all the dependencies of a class through the constructor, you might get a risk of decreasing reusability, encapsulation and integrity of that class.

To give you an illustrative example, let's say the bellow image represents a dependency tree for some class A:

Dependency graph illustrative example

Even if none of the types on the image has more than 3 dependencies, the dependency graph is still quite large (18 dependencies). If we used constructor injection for all the dependencies, we would have to instantiate A the following way:

A a = new A(b, c, d);

where b, c and d are of types B, C and D respectively which means we first had to resolve B, C and D and all their dependencies. So, the instantiation of A actually looks like this:

P p = new P();
O o = new O();
N n = new N();
E e = new E(n, o, p);
F f = new F();
B b = new B(e, f);
M m = new M();
L l = new L();
K k = new K();
G g = new G(k, l, m);
C c = new C(g);
J j = new J();
S s = new S();
R r = new R();
Q q = new Q();
I i = new I(q, r, s);
H h = new H();
D d = new D(h, i, j);

A a = new A(b, c, d);

If we used some dependency container to resolve all those dependencies, there will be less code when instantiating A, but we would still have to map all those dependencies in the container. All of this, just to use one class.

Imagine the following situation: You are looking for some library you can download and include in your project that will help you solve some problem. Now imagine you found a library that seems useful, then you start reading the documentation and it says that in order to use one of the classes, you need to resolve 18 dependencies first. What would you do? I bet you would continue looking for some other, more (re)usable one.

Possible solutions

We should decide which dependencies should we hide from the user and which ones should we leave to them to resolve. In order to do so, we should ask the following questions?

  • Which dependencies should be public?
  • Which dependencies can be used to customize or extend our class?
  • Which dependencies are just implementation details?

Dependencies that fall into first AND second category (dependencies that should be visible from the outside of the module and can be used to customize or extend the containing class or module) should be injected through the constructor of the class.
Dependencies that do not fall into first two categories, and especially dependencies that fall into the third category, should not be injected through the constructor of the class. By making the user of our class resolve those dependencies, we reduce the reusability of the class. If the dependency falls into the third category, making the user resolve it will also increase the fragility of the code.

Let's examine possible solutions on an example. We have BlogService class that we want to use to publish blog posts.

blog-publish-dependencies-example

Our BlogService has the following dependencies:

  • UserManagementService for handling things like who the user is, whether they are allowed to publish post etc.
  • IBlogRepository for retrieving and persisting post state.
  • INotificationSender for sending notifications about post publishing to blog owners and other people.
  • PostPublisher for preparing the post for publishing and publishing it.

I left out some of dependencies for the sake of simplicity. I also think that for the purpose of this example, it doesn't make a difference whether some dependencies are interfaces (whether we are inverting them) or concrete classes. For example, I could have added IPostPublisher interface and make BlogService depend on it instead of concrete class, but it wouldn't make any difference and it is a different topic.

If we injected all the dependencies through the constructor of BlogService, it would have looked like this:

public BlogService(PostPublisher postPublisher, UserManagementService userManagementService, IBlogRepository blogRepository, INotificationSender notificationSender) { ...

Imagine if we applied dependency injection to all the classes in the dependency graph. The user would have to resolve all the dependencies, including DpcmEncoder, MetatagGenerator, DbContext, and if they decide to use email notifications, then also SmtpClient and BinaryConverter. Why would the user have to resolve dependencies like DpcmEncoder or BinaryConverter in order to use BlogService. The user should not even be aware of those classes. Those are internal dependencies that are just implementation details and we should not make the user resolve them and depend on them. On the other hand, for dependencies like INotificationSender, it might be ok to allow the user to be aware of them and use them to customize the behavior of BlogService. So, let's categorize those dependencies:

Which dependencies should be public? INotificationSender, IBlogRepository, UserManagementService, PostPublisher

Which dependencies can be used to customize or extend our class? INotificationSender, IBlogRepository

Which dependencies are just implementation details? All the others

Even if UserManagementService and PostPublisher are not implementation details, we don't need to make the user resolve them. Those dependencies are vital parts of BlogService and if we let user customize those parts (e.g. change the way user permissions work by providing different implementation of UserManagementService) we could violate the integrity of BlogService.

So, INotificationSender and IBlogRepository are the only dependencies that we could inject through the constructor. That way, our constructor would look like this:

public BlogService(IBlogRepository blogRepository, INotificationSender notificationSender) {
    ...
    //other dependencies are resolved internally
}

This way, we are letting the user of BlogService to use various notification mediums and database providers.

We can make this even simpler for the user. For example, if our blog module already has some implementations of INotificationSender (e.g. email, Slack, SMS...) we can let the user decide to use one of those without having to resolve it. Instead of passing the implementations of dependencies to the constructor, the user could instead pass an object containing their preferences. We can name that object's class BlogOptions and it can look like this:

public class BlogOptions
{
	public string ConnectionString { get; private set; }
	public DatabaseProvider DatabaseProvider { get; private set; }
	public IBlogRepository BlogRepository { get; private set; } //This will be used if DatabaseProvider is Custom
	public NotificationMedium NotificationMedium { get; private set; }
	public INotificationSender NotificationSender { get; private set; } //This will be used if NotificationMedium is Custom
	//...

	public class BlogOptionsBuilder
	{
		private BlogOptions options;

		public BlogOptionsBuilder()
		{
			options = new BlogOptions();
		}

		public BlogOptionsBuilder UseSqlStorage(string connectionString)
		{
			options.DatabaseProvider = DatabaseProvider.Sql;
			options.ConnectionString = connectionString;
			return this;
		}

		public BlogOptionsBuilder UseFileStorage(string connectionString)
		{
			options.DatabaseProvider = DatabaseProvider.File;
			options.ConnectionString = connectionString;
			return this;
		}

		public BlogOptionsBuilder UseStorage(string connectionString, IBlogRepository blogRepository)
		{
			options.DatabaseProvider = DatabaseProvider.Custom;
			options.ConnectionString = connectionString;
			options.BlogRepository = blogRepository;
			return this;
		}

		public BlogOptionsBuilder UseEmailNotifications()
		{
			options.NotificationMedium = NotificationMedium.Email;
			return this;
		}

		public BlogOptionsBuilder UseSlackNotifications()
		{
			options.NotificationMedium = NotificationMedium.Slack;
			return this;
		}

		public BlogOptionsBuilder UseNotifications(INotificationSender notificationSender)
		{
			options.NotificationMedium = NotificationMedium.Custom;
			options.NotificationSender = notificationSender;
			return this;

		}

		//...

		public BlogOptions Build()
		{
			//Perform some validation and other stuff
			return options;
		}
	}
}

And then the constructor of BlogService would only accept BlogOptions:

public BlogService(BlogOptions options) { ...

So, the user will be able to customize the behavior of our class using BlogOptionsBuilder. For example, if they wanted to use BlogService with SQL database and email notifications, they would instantiate it the following way:

BlogOptions blogOptions = new BlogOptionsBuilder()
	.UseEmailNotifications()
	.UseSqlStorage("some connection string")
	.Build();
BlogService blogService = new BlogService(blogOptions);

And if they wanted to use their custom implementations of IBlogRepository and INotificationSender, they would do it this way:

BlogOptions blogOptions = new BlogOptionsBuilder()
	.UseNotifications(someNotificationSenderImplementation)
	.UseStorage("some connection string", someRepositoryImplementation)
	.Build();
BlogService blogService = new BlogService(blogOptions);

This way, we exposed some dependencies as public and allowed the user to customize the behavior of BlogService, but we didn't force the user to resolve our dependencies. E.g, if the user wants to provide a custom implementation of INotificationSender, they can do it, but they can also use one of the existing implementations without depending on them.

Possible implementation of the BlogService constructor could look like this:

public class BlogService
{
	private PostPublisher postPublisher;
	private UserManagementService userManagementService;
	private IBlogRepository blogRepository;
	INotificationSender notificationSender;

	public BlogService(BlogOptions options) {
		//resolve blog repository
		if (options.DatabaseProvider == DatabaseProvider.Custom)
			blogRepository = options.BlogRepository;
		else
			blogRepository = FactoryProvider.RepositoryFactory.CreateBlogRepository(options.ConnectionString, options.DatabaseProvider);

		//resolve notification sender
		if (options.DatabaseProvider == DatabaseProvider.Custom)
			notificationSender = options.NotificationSender;
		else
			notificationSender = FactoryProvider.NotificationSenderFactory.CreateNotificationSender(options.NotificationMedium);

		//resolve other dependencies (you can use DI container, factories, direct instantiation using 'new'...)
		postPublisher = ...
		userManagementService = ...
	}

	//...
}

The code in the constructor that resolves dependencies based on options can be extracted to separate methods or organized to your liking. You could also make the constructor put data from BlogOptions to some place where factories can find it (e.g. static class) so it doesn't have to pass it to them.

There are also trade-offs with this approach, because you are making the class (in this case BlogService) aware of factories or dependency containers, so you should decide what is more important to you in which situation.

What about testability?

There are ways to make a class testable and apply Inversion of control without dependency injection. Let's say you have an IoC container or a set of factories that are used to provide you with a concrete implementation of the abstraction you request from them. The typical approach with dependency injection would be to resolve the dependencies of some class before instantiating it and then passing those dependencies to the constructor of the class. Then, if you want to write unit tests for that class, you just create mock objects of those dependencies and pass them to the constructor of the class. Another approach would be for the class to use its own IoC container or factories to resolve its dependencies instead of accepting them in constructor. But, how do you mock dependencies if you cannot pass them through the constructor? Well you can mock the IoC container or factories that the class is using. You make those mocks resolve dependencies of the class to mocked objects instead of real implementations. Then you let the class use mocks of IoC or factories in unit tests. Since we are mocking IoC or factories, does that mean we should inject them through the constructor? Well that would have the same effect as injecting all the dependencies through the constructor. Instead, you could make IoC or factories available to unit tests as static properties of some class. Then, before invoking the method of the class, unit tests will first set that static property to mocked IoC or factory. In BlogService scenario, we can create FactoryProvider class which will have a static property that contains factories:

static class FactoryProvider
{
	private static readonly object _lock = new object();
	private static IRepositoryFactory _repositoryFactory;
	//...

	internal static IRepositoryFactory RepositoryFactory
	{
		get
		{
			lock (_lock)
			{
				if (_repositoryFactory == null)
				{
					_repositoryFactory = new RepositoryFactory();
				}
				return _repositoryFactory;
			}
		}
		set
		{
			lock (_lock)
			{
				_repositoryFactory = value;
			}
		}
	}

	//...
}

And then when writing a unit test, we will set the RepositoryFactory property to a mocked version of factory:

[Fact]
public void SomeTestForPostPublishing()
{
	//Arrange
	Mock<IRepositoryFactory> repositoryFactoryMock = new Mock<IRepositoryFactory>();
	//... perform setups on the mock
	FactoryProvider.RepositoryFactory = repositoryFactoryMock.Object; //Replace factory with a mock
	// ...
}

So, when invoked from the unit test, the following line of BlogService constructor:

blogRepository = FactoryProvider.RepositoryFactory.CreateBlogRepository(options.ConnectionString, options.DatabaseProvider);

will use the mock of the repository factory, which will create a mock of IBlogRepository.

This approach is not perfect because it forces you to expose IoC or factories as static properties and access those properties from unit tests. However, in some situations it might be a lower price to pay than injecting all the dependencies through the constructor.

Summary

In order to preserve reusability, encapsulation and integrity of a class, you might want to move the resolving of some dependencies inside the class instead of leaving that responsibility to the user of the class. However, there are some trade-offs with this approach, so you should carefully decide when to use which approach and what is more important to you. The purpose of this post was not to make you think dependency injection is bad, but to help you realize if you are abusing it and what are the trade-offs.