Dependency Injection: An introduction

What is dependency injection?

Dependency injection is often confused with inversion of control. Inversion of control is a design pattern in which your classes delegate the responsibility of creating and disposing of any dependencies it might have. Dependency injection is a design pattern that supports inversion of control, in which a class requires any dependencies it might have to be passed into it as parameters, usually through its constructor. 

Quick preface: If you don’t have a strong grasp of what abstractions are, I strongly recommend reading this article first before continuing. Alright, let’s continue!

Let’s say you have a class called InvoiceService. This class depends on another class, namely SQLInvoiceRepository. Usually, developers would spin up a new instance of SQLInvoiceRepository inside InvoiceService. This seems to make sense at first glans. The InvoiceService requires the SQLInvoiceRepository, therefore it should be responsible for creating it, and when it no longer requires it, it should also be responsible for disposing of it. The code would look something like this:

public class InvoiceService
{
    private SQLInvoiceRepository sqlInvoiceRepo;
    public InvoiceService()
    {
        sqlInvoiceRepo = new SQLInvoiceRepository();
    }
}

Problems

InvoiceService is now tightly coupled with the SQLInvoiceRepository. What happens if you someday want to replace the SQL repository with an Entity Framework repository? You will have to revisit InvoiceService and change that code as well.

An instance of SQLInvoiceRepository could be very resource intensive to spin up. It might take a long time or require a large amount of memory. What if other parts of the application also require an SQLInvoiceRepository? Should they each have their own instance of it, even if one instance could serve them all?

It is now also very hard to perform unit tests on InvoiceService. Unit tests revolve around testing isolated pieces of code. If you made no changes to the code and where to test InvoiceService, you would be testing SQLInvoiceRepository as well. You could write code that used a different repository in a testing environment, but things would get messy pretty quickly.

Solutions

Inversion of control and dependency injection solve these problems rather elegantly. Dependency injection means “I am no longer responsible for managing any dependencies I might need. Just make sure I have them when you need me.” We would change the above code to something like this:

public class InvoiceService
{
    private SQLInvoiceRepository sqlInvoiceRepo;
    public InvoiceService(SQLInvoiceRepository repository)
    {
        sqlInvoiceRepo = repository;
    }
}

We are delegating the responsibility of creating our dependency on SQLInvoiceRepository to our parent class. With the help of abstraction, we immediately solve the first problem. If instead of a SQL repository, we just received SOME repository, that fulfils the contract we require, we don’t care about the implementation, as long as it does what we need. Now, we can require an InvoiceRepository (either an abstract class or an interface) as a dependency to InvoiceService, and make sure that SQLInvoiceRepository fulfils that contract by either implementing an interface or inheriting the InvoiceRepository abstract class and overriding its abstract methods and members.

We no longer need to revisit InvoiceService if we replace SQLInvoiceRepository with EntityInvoiceRepository. We just have to make sure that they both implement InvoiceRepository and inject it into InvoiceService from the beginning.

This also solves the problem of resource-intensive classes. Let our parent class spin a single new instance of it up and pass it around to any other classes that require it.

Finally, this also solves the problem of test-ability. We can simply create a new TestRepository class that implements InvoiceRepository. We now control how it behaves, and this makes it infinitely easier for us to test our InvoiceService and see how it reacts with any kind of response from InvoiceRepository.

What about parent classes?

Well, OrderService depends on InvoiceService, so if we continue with the same attitude as before, where we don’t want to be responsible for creating any of our dependencies, our code would look something like this:

public class OrderService
{
    private readonly InvoiceService invoiceService;
    public OrderService(InvoiceService invoiceService)
    {
        this.invoiceService = invoiceService;
    }
}

Where does it all stop then? How far does the rabbit hole go? At some point, we have to stop and instantiate these classes, right? Yes, we do, and we do that at the application root. The application root is the closest possible place to our applications entry point. Something like the main method. In a C# console program, it would like like this:

class Program
{
    static void Main(string[] args)
    {
        var orderservice = 
            new OrderService(
                new InvoiceService(
                    new SQLInvoiceRepository()));
    }
}

As the dependency tree (or object graph, as it is called) grows, the more complicated it is to build. Doing this by hand is prone to error.

Luckily, there are quite a few tools that help us wiring up the object graph. We will talk about them in another post.

The bad

Unfortunately, dependency injection is not the solution for everything. Depending on the framework, it might take a while for new developers to get acquainted with how everything is wired up correctly. It might also sometimes seem like magic. You can mitigate this with good communication. Fortunately, most frameworks are also very well documented.

The biggest problem though is that dependency injection is either all or nothing. Because of the way the object graph is created, you can’t implement dependency injection in only certain parts of the application. While you can create smaller trees in new parts of the system, you aren’t really giving up control of your dependencies, you are just pushing them slightly further up.

Conclusion

If you wish to read more about dependency injection, I strongly recommend reading Dependency Injection in dot net by Mark Seemann, or just read some stuff on his blog, it’s really interesting.

I hoped you enjoyed this post! Next time, we will be looking at some of the frameworks and different ways of wiring your application up. Stay tuned!

3 thoughts on “Dependency Injection: An introduction

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.