Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

When can Dependency Injection Containers become too big, and what can I do about it?

We all know the why Dependency Injection is awesome because it makes code less coupled, easier to test, and much nicer to read! And then some decide to use a Dependency Injection Container like pimple for PHP to assist with the Dependency Inversion principle in SOLID.

So when creating your DiC using pimple, passing it through to the controller, and having all your new objects created in closures that are actually only instantiated when the developer calls $container['object'], this is great!

But what happens when you have a very large set of classes in your application? Say 1000+, and you want these available in the container?

Development-wise, this is going to be a nightmare placing these all within one file. What would be the best way to separate them, or would an alternative suggestion be preferable?

On the separation side, how about:

  • Creating the container
  • Including several files with the classes grouped together depending on the application
  • Adding to the container incrementally until the end of the file includes

On the flip side, I know Symfony2 uses XML/YAML for the DiC configuration, but really this doesn't go into much talk about the architectural side of things when an application contains so many classes.

What can a developer do to when they have such a large codebase?

like image 687
Jimbo Avatar asked May 09 '13 12:05

Jimbo


People also ask

How much dependency injection is too much?

The fact your class has so many dependencies indicates there are more than one responsibilities within the class. Often there is an implicit domain concept waiting to be made explicit by identifying it and making it into its own service. Generally speaking, most classes should never need more than 4-5 dependencies.

Which problem can you solve using dependency injection?

The goal of the dependency injection technique is to remove this dependency by separating the usage from the creation of the object. This reduces the amount of required boilerplate code and improves flexibility.

What is the major limitation of dependency injection?

Disadvantages of Dependency Injection: Dependency injection creates clients that demand configuration details to be supplied by construction code. This can be difficult when obvious defaults are available. Dependency injection can make code difficult to trace (read) because it separates behaviour from construction.


2 Answers

Let's say that all of your classes are going to be called via the DI container.

First a little comparative case: If you go to a store to buy a pair of trousers, then you are using DI at several moments. For instance you do not have to tell what size you need, the people that help you have the expertise to find out. You will have to say what kind of type of trousers you want to have, the people of the store will present you some trousers according to your wishes (or let you know that that is impossible). An example which is not DI is the answer to the question where all those trousers come from. For a customer entering the store is the answer to that question totally irrelevant. It will never be the reason to enter the shop, where the need for a pair of trousers of a certain type is. You can ask unspecified questions of a certain type and you will get exact answers. That is the core of DI. How it is done is not of your business nor your concern. But you can not ask anything. You can't buy bread in a cloth store for instance. The questions must be related to the specific DI subject (interface).

Pimple is an associative array. It is not a DI - container. As far as you know, will the DI - container return an interface, where the DI - container knows which implementation to load. Let me give you an example to show you where Pimple is not a DI - container. You are in the store, you have chosen a pair of trousers and you want to buy it. Now you are going to pay. How? That is DI again. For you it is no problem, you have several methods for the transaction available and you will choose one. How the system will answer is not interesting for you: you will say nothing, just get your wallet and start paying.

In Pimple you will have to select the object to use for payments. The client will have to say 'I need the payment object'. In a true DI - container will you just have to hand over the money in a legal way (interface behavior) and based on the input data will the DI - container know which implementation to choose (cash, credit card, master card). You do not have to worry about that. If you still would have to worry about that, why call it dependency injection? Pimple is at his best a service locator, but to use DI you will have to use an interface. Otherwise is there nothing to hide, no dependency to inject. :-)

So, don't use Pimple for DI. It is not a DI - container. If you are going to use Pimple (and it can organize your classes well) then do not call it DI. It is at its best a service locator, but it is not hiding the implementation using interfaces. Hence it can not be DI.

Try to organize your codebase. What are the functions of the different classes? Which classes need DI? I suggest you only use DI for classes that execute functional requirements. When you go back to the case of the store: all functions in which the store personnel is communicating with the client directly. Not for the implementation how to walk to the back end trying to find another pair of trousers. Not for the process how to open or close the shop. But yes for how to welcome a client and asking what type of trousers someone wants to have. In your application: are there interfaces/classes which are used directly interacting with the visitors of the application and could you create some type of contract how to describe the interaction? That is the design of that DI - container. I would not use DI all over the place. DI comes with a performance penalty and a maintenance problem. The more DI you use, the more layers you will have, the less you will know what happens where. Use DI preferrably where it is most beneficial and that is where it is most likely that the implementation will change yet the caller will not know that the interface has changed nor is the caller interested in such a change. If you take that as a guideline, then can you make distinctions which classes to hide via a DI - container and which classes not.

like image 58
Loek Bergman Avatar answered Nov 09 '22 21:11

Loek Bergman


I'd like to answer this myself now.

Dependency Injection Containers do not contain every object in your application. The fact that they are labelled containers is the main problem here. "DiC's" like Symfony's Pimple are not dependency injectors. They are glorified associative arrays used to hold objects and primarily advocate the Service Locator anti-pattern.

See $this->get('something') in your code, like in Symfony? That's a service locator. Use that and you are hiding class dependencies and making a liar of your object API. This includes your controllers!

Object requirements should be readable from the constructor or method signatures of an object only, and a provided to this object via Dependency Injection providing Inversion of Control through your codebase helping testability, maintainability and flexibility with greatly simplified polymorphism being made available through it.

Dependency injection containers should be renamed to injectors, because that is what they should be doing - injecting dependencies for you. You should not be pulling objects out of them when you require. How are you going to use DI and inversion of control if you just pull them out when you need them using a service locator?

In real life you wouldn't build a house by transporting the entire hardware store (hopefully) to the construction site so you can access any parts you need. Instead, the foreman (__construct()) asks for the specific parts that will be needed (Door and Window) and goes about procuring them. Your objects should function in the same way; they should ask only for the specific dependencies required to do their jobs. Giving the House access to the entire hardware store is at best poor OOP style and at worst a maintainability nightmare. rdlowrey - auryn

You could use reflection to read object requirements then instantiate and inject dependencies automatically. Auryn is a fantastic injector for this. You set it up in your bootstrap and controller resolver and then you can write SOLID code for conrete auto-injection based on object typehints throughout your codebase. Any abstract classes / interfaces? No problem, you alias concretes to these either directly in-code or by reading them from a config file (preferable) and aliasing them in a loop that way. Using a configuration file means you can have config-based polymorphism at your disposal - the ability to switch out one concrete implementation with another simply by creating the new object and changing one string in a config file is fantastic.

In conclusion, Dependency Injection Containers never become "too big" unless you're doing it wrong and using them as a service locator or a glorified associative array of objects! You don't shove all your objects in there in order to pull them out when you need them. I don't care if they're lazy-loaded. Use an actual injector. And don't even mention Laravel or "facades".

like image 29
Jimbo Avatar answered Nov 09 '22 21:11

Jimbo