8

I would like to be sure I understand the concept of dependency injection (DI). Well, I actually understand the concept, DI is not complicated: you create an interface then you pass the implementation of my interface to the class that use it. The common way to pass it is by constructor but you can also pass it by setter or other method.

What I'm not sure to understand is when to use DI.

Usage 1: Of course using DI in case you have multiple implementation of your interface seems logic. You have a repository for your SQL Server then another one for your Oracle database. Both share the same interface and you "inject" (this is the term used) the one you want on runtime. This is even not DI, this is basic OO programming here.

Usage 2: When you have a business layer with many services with all they specific methods it seems the good practice is to create an interface for each service and also inject the implementation even if this one is unique. Because this is better for maintenance. This is this second usage I don't understand.

I have something like 50 business classes. Nothing is common between them. Some are repositories getting or saving data in 3 different databases. Some read or write files. Some do pure business action. There are also specific validator and helpers. The challenge is memory management because some classes are instanced from different locations. A Validator can call several repositories and other validators that can call the same repositories again.

Example: Business layer

public class SiteService : Service, ICrud<Site>
{
    public Site Read(Item item, Site site)
    {
        return beper4DbContext.Site
            .AsNoTracking()
            .SingleOrDefault(y => y.SiteId == site.Id && y.ItemId == item.Id)
    }

    public Site Read(string itemCode, string siteCode)
    {       
        using (var itemService = new ItemService())
        {
            var item = itemService.Read(itemCode);
            return Read(item, site);
        }
    }
}
public class ItemSiteService : Service, ICrud<Site>
{
    public ItemSite Read(Item item, Site site)
    {
        return beper4DbContext.ItemSite
            .AsNoTracking()
            .SingleOrDefault(y => y.SiteId == site.Id && y.ItemId == item.Id)
    }

    public ItemSite Read(string itemCode, string siteCode)
    {        
        using (var itemService = new ItemService())
        using (var siteService = new SiteService())
        {
            var item = itemService.Read(itemCode);
            var site = siteService.Read(itemCode, siteCode);
            return Read(item, site);
        }
    }
}

Controller

public class ItemSiteController : BaseController
{
    [Route("api/Item/{itemCode}/ItemSite/{siteCode}")]
    public IHttpActionResult Get(string itemCode, string siteCode)
    {
        using (var service = new ItemSiteService())
        {
            var itemSite = service.Read(itemCode, siteCode);
            return Ok(itemSite);
        }
    }
}

This example is very basic but you see how I can easily create 2 instances of itemService to get an itemSite. Then also each service come with his DB context. So this call will create 3 DbContext. 3 Connections.

My first idea was to create singleton to rewrite all this code like bellow. The code is more readable and most important the singleton system create only one instance of each service used and create it on first call. Perfect, except I still have differents context but I can do the same system for my contexts. So done.

Business layer

public class SiteService : Service, ICrud<Site>
{
    public Site Read(Item item, Site site)
    {
        return beper4DbContext.Site
            .AsNoTracking()
            .SingleOrDefault(y => y.SiteId == site.Id && y.ItemId == item.Id)
    }

    public Site Read(string itemCode, string siteCode)
    {       
            var item = ItemService.Instance.Read(itemCode);
            return Read(item, site);
    }
}
public class ItemSiteService : Service, ICrud<Site>
{
    public ItemSite Read(Item item, Site site)
    {
        return beper4DbContext.ItemSite
            .AsNoTracking()
            .SingleOrDefault(y => y.SiteId == site.Id && y.ItemId == item.Id)
    }

    public ItemSite Read(string itemCode, string siteCode)
    {        
            var item = ItemService.Instance.Read(itemCode);
            var site = SiteService.Instance.Read(itemCode, siteCode);
            return Read(item, site);       
    }
}

Controller

public class ItemSiteController : BaseController
{
    [Route("api/Item/{itemCode}/ItemSite/{siteCode}")]
    public IHttpActionResult Get(string itemCode, string siteCode)
    {
            var itemSite = service.Instance.Read(itemCode, siteCode);
            return Ok(itemSite);
    }
}

The some people tell me according to good practice I should use DI with single instance and using singleton is bad practice. I should create an interface for each business class and instantiate it with help of DI container. Really? This DI simplify my code. Hard to believe.

closed as too broad by gnat, Robert Harvey Jul 2 at 4:42

Please edit the question to limit it to a specific problem with enough detail to identify an adequate answer. Avoid asking multiple distinct questions at once. See the How to Ask page for help clarifying this question. If this question can be reworded to fit the rules in the help center, please edit the question.

  • 4
    DI has never been about readability. – Robert Harvey Jul 1 at 14:40
14

The most "popular" use case for DI (apart from the "strategy" pattern usage you already described) is probably unit testing.

Even if you think there will be only one "real" implementation for an injected interface, in case you do unit testing, there is usually a second one: a "mock" implementation with the only purpose of making an isolated test possible. That gives you the benefit of not having to deal with the complexity, the possible bugs, and maybe the performance impact of the "real" component.

So no, DI is not for increasing readability, it is used for increasing testability ( - of course, not exclusively).

This is not an end in itself. If your class ItemService is a very simple one, which is does not make any external network or database access, so it does not hinder writing unit tests for something like SiteService, testing the latter in isolation may be worth the effort, thus DI won't be necessary. However, if ItemService is accessing other sites by network, you will probably want to unit test SiteService decoupled from it, which can be accomplished by replacing the "real" ItemService by a MockItemService, which delivers some hard-coded fake items.

Let me point out another thing: in your examples, one might argue that one won't need DI here for testing the core business logic - the examples show always two variants of the Read methods, one with the real business logic involved (which can be unit tested without DI), and one which is just the "glue" code for connecting the ItemService to the former logic. In the shown case, that is indeed a valid argument against DI - in fact, if DI can be avoided without sacrificing testability that way, then go ahead. But not all real-world code is so simple, and often DI is the most simple solution to achieve "enough" unit testability.

  • 1
    I really get it but what if I don't do unit testing. I do integration testing with a fake database. Change are operate too often to maintain unit tests and our production data change all the time. So testing with copy of real data is the only way we can seriously test. In place of Unit Testing we protect all our methods with code contracts. I know this is not the same and I know unit testing in a plus but I don't have the time to put priority on unit testing. That is a fact, not a choice. – Bastien Vandamme Jul 2 at 1:08
  • 1
    @BastienVandamme: there is a large space between "no unit testing at all" and "we unit test everything". And I have never seen a huge project which could not find some benefits in using unit tests at least for some parts. Identify those parts, then check if DI is required to make them unit testable. – Doc Brown Jul 2 at 2:38
4

By not using dependency injection you allow yourself to create permanent connections to other objects. Connections you can hide inside where they'll surprise people. Connections that they can only change by rewriting what you're creating.

Rather than that you can use dependency injection (or reference passing if you're old school like me) to make what an object needs explicit without forcing it to define how it's needs must be fulfilled.

This does force you to accept many parameters. Even ones with obvious defaults. In C# you lucky sods have named and optional arguments. That means you have default arguments. If you don't mind being statically bound to your defaults, even when you don't use them, you can allow DI without being overwhelmed with options. This follows convention over configuration.

Testing is not a good justification for DI. The moment you think it is someone will sell you a wiz bang mocking framework that uses reflection or some other magic to convince you that you can go back to the way you worked before and use magic to do the rest.

Used correctly, testing can be a good way to show if a design is isolated. But that isn't the point of it. It doesn't stop salespeople from trying to prove that with enough magic everything is isolated. Keep the magic to a minimum.

The point of this isolation is to manage change. It's nice if one change can be made in one place. It's not nice to have to follow it through file after file hoping that the madness will end.

Put me in a shop that refuses to do unit testing and I'll still do DI. I do it because it lets me separate what's needed from how it's done. Testing or no testing I want that isolation.

  • 2
    Can you elaborate on why testing is not a good justification for DI? You seem to be conflating it with magical reflection frameworks - and I understand disliking those - but not make any counterpoint to the idea itself. – Jacob Raihle Jul 1 at 13:34
  • 1
    The mocking argument seems like a slippery slope. I don't use mocking frameworks, but I understand the benefits of DI. – Robert Harvey Jul 1 at 14:22
  • 2
    It's merely a cautionary tail. When people think enabling automated testing is the only reason to do DI they not only miss out on understanding the benefits to design flexibility they also become vulnerable to sales pitches for products promising benefits without the work. DI is work. There's no free lunch. – candied_orange Jul 1 at 17:49
  • 3
    To put it another way, decoupling is the real reason for DI. Testing is simply one of the most common areas where the benefits of decoupling are evident. – TKK Jul 1 at 21:05
  • 2
    Hey, no joke. If DI doesn't help you maintain your code don't use it. – candied_orange Jul 2 at 3:10
2

The helicopter view of DI is simply the ability to swap out an implementation for an interface. While this of course is a boon for testing there are other potential benefits:

Versioning implementations of an object

If your methods accept interface parameters in the middle layers, you're free to pass whatever implementations you like in the top layer which reduces the amount of code that needs to be written to swap out implementations. Admittedly this is a benefit of interfaces anyway, but if the code is written with DI in mind, you'll achieve this benefit out of the box.

Reducing the number of objects that need to pass through layers

While this mainly applies to DI frameworks, if object A requires an instances of object B, it is possible to query the kernel (or whatever) to generate object B on the fly rather than pass it through the layers. This reduces the amount of code that needs to be written and tested. It also keeps layers that don't care about object B clean.

2

It is not necessary to use interfaces to use DI. The primary purpose of DI is to separate construction and usage of objects.

Using singletons is rightly frowned upon in most cases. One of the reasons is that it becomes very difficult to get an overview of what dependencies a class has.

In your example the ItemSiteController could simply take a ItemSiteService as a constructor argument. This lets you avoid any cost of creating objects, but avoid the inflexibility of a singleton. The same is true of ItemSiteService, if it needs a ItemService and a SiteService, inject them in the constructor.

The benefit is greatest when all objects use dependency injection. This allows you to centralize construction to a dedicated module, or delegate it to a DI container.

A dependency hierarchy might look something like this:

public interface IStorage
{
}

public class DbStorage : IStorage
{
    public DbStorage(string connectionString){}
}

public class FileStorage : IStorage
{
    public FileStorage(FileInfo file){}
}

public class MemoryStorage : IStorage
{
}

public class CachingStorage : IStorage
{
    public CachingStorage(IStorage storage) { }
}

public class MyService
{
    public MyService(IStorage storage){}
}

public class Controller
{
    public Controller(MyService service){}
}

Note that there is only one class without any constructor parameters, and only one interface. When configuring the DI container you can decide what storage to use, or if caching should be used etc. Testing is easier since you can decide on what database to use, or to use some other kind of storage. You can also configure the DI container to treat objects as singletons if needed, within the context of the container object.

  • "In your example the ItemSiteController could simply take a ItemSiteService as a constructor argument." Of course but a controller can use from 1 to 10 services. Then sometime I can add one new service forcing me to change the constructor signature. How can you say this is better for maintenance? And no this will probably cost me to create the object multiple time because I don't have the singleton mechanism anymore. So I will have to pass it to each controller using it. How can I ensure any developer is not always recreating it. What system can ensure I only create it once? – Bastien Vandamme Jul 2 at 1:34
  • I think, like most people, you are mixing the DI pattern with the Container Feature. Your example is my Usage 1. I do not challenge this one. When you have multiple implementation of an interface you must use what you call DI. I call that OOP. Whatever. I challenge the Usage 2 where most of people seems to use it for testing and maintenance. I understand but this doesn't resolve my single instance request. Hopefully Container seems to offer a solution. Container… not only DI. I think I need DI with a single instance feature and I can find this in containers (3rd party libraries). – Bastien Vandamme Jul 2 at 1:51
  • @bastien-vandamme If you need to add new services then you should add them to the constructor, this should not be a problem. If you need to inject many services this can indicate a problem with the architecture, not the idea to inject the dependencies. You ensure that the same instance is reused by other developers by good design and training your colleagues. But it should in general be possible to have multiple instances of a class. As an example, I might have multiple database instances and have a service for each one. – user104903 Jul 2 at 10:07
  • Euh no. I have a database with 50 tables, I need 50 repositories, 50 services. I can work with generic repositories but my database far from being normalized and clean so whatever my repositories or service at some point must have specific code because of history. So I cannot do all generic. Each service has specific business rules I need to maintain separately. – Bastien Vandamme Jul 4 at 3:12
2

You isolate external systems.

Usage 1: Of course using DI in case you have multiple implementation of your interface seems logic. You have a repository for your SQL Server then another one for your Oracle database. Both share the same interface and you "inject" (this is the term used) the one you want on runtime. This is even not DI, this is basic OO programming here.


Yes, use DI here. If it is going to the network, database, filesystem, another process, user input, etc. You want to isolate it.

Using DI will ease testing because you will easily mock these external systems. No, I am not saying that is the first step towards unit testing. Nor that you cannot do testing without doing this.

Futhermore, even if you did have only one database, using DI would help you the day you want to migrate. So, yes, DI.

Usage 2: When you have a business layer with many services with all they specific methods it seems the good practice is to create an interface for each service and also inject the implementation even if this one is unique. Because this is better for maintenance. This is this second usage I don't understand.

Sure, DI can help you. I would debate on the containers.

Perhaps, something worth noting, is that dependency injection with concrete types is still dependency injection. What matters is that you can create custom instances. It does not have to be interface injection (although interface injection is more versatile, that does not mean you should use it everywhere).

The idea of creating an explicit interface for each and every class has to die. In fact, if your would have only one implementation of an interface... YAGNI. Adding an interface is relatively cheap, can be done when you need it. In fact, I would suggest waiting until you have two or three candidate implementations so you have a better idea of what things are common among them.

However, the flip side of that, is that you could create interfaces that matches closer what the client code needs. If the client code only needs a few members of a class, you can have an interface just for that. That will lead to better interface segretation.


Containers?

You know you do not need them.

Let us bring it down to trade-offs. There are cases where they are not worth it. You will have your class take the dependencies it needs on constructor. And that could be good enough.

I'm really not a fan of annotating attributes for "setter injection", much less third party ones, I get it might be necessary for implementations beyond your control… however, if you decide to change library these have to change.

Eventually you will start building up routines to create these objects, because to create it, you first need to create these others, and for those you need some more...

Well, when that happens you want to put all that logic in a single place and reuse it. You want a single source of truth about how you create your object. And you get it by not repeating yourself. That will simplify your code. Makes sense, right?

Well, where do you put that logic? The first instinct will be to have a Service Locator. A simple implementation is a singleton with a read-only dictionary of factories. A more complex implementation could use reflection to create the factories when you haven't provided one.

However, using a singleton or static service locator will means that you will be doing something like var x = IoC.Resolve<?> every where you need to create an instance. Which is adding a strong coupling to your service locator/container/injector. That can actually make unit testing harder.

You want an injector that you instantiate, and keep it only to be used on the controller. You do not want it going deep into the code. That could actually make testing harder. If some part of your code needs it to instantiate something, it should be expecting an instance (or a most a factory) on its constructor.

And if you are having a lot of parameters on the constructor... see if you have parameters that travel together. Chances are you can merge parameters into descriptor types (value types ideally).

  • Thank you for this clear explanation. So according to your I should create a injector class that contains the list of all my services. I don't inject service by service. I inject my injector class. This class manage also the single instance of each of my service? – Bastien Vandamme Jul 2 at 2:34
  • @BastienVandamme you can couple you controllers with the injector. So, if you mean passing it to the controller, then I agree. And yes, it can handle having a single instance of the services. On the other hand, I would worry if the controller is passing the injector around... it can pass factories around if needed. In fact, the idea is separating the services from the initialization of their dependencies. So, ideally, the services are not aware of the injector. – Theraot Jul 2 at 2:46
  • "Using DI will ease testing because you will easily mock these external systems." I have found that relying on mocking these systems in unit testing without first minimizing the code that has any connection to them severely reduces the utility of having unit tests. First maximize the amount of code that can be tested via input/output testing only. Then look at whether mocking is necessary. – jpmc26 Jul 2 at 3:43
  • @jpmc26 agreed. Addendum: we should isolate those external system anyway. – Theraot Jul 2 at 3:45
  • @BastienVandamme I has been thinking about this. I think that if you do not lose the ability to create to create custom injectors to pass around, doing so should be fine. – Theraot Jul 2 at 16:14

Not the answer you're looking for? Browse other questions tagged or ask your own question.