I used IoC containers for the first 5 years of my career. The last year I have been using them very little. This is due to a job change and in my current team we rarely use them. I have nothing against trying something new and it is always great fun and a learning experience to try something new.
Why am i mentioning this? To state that I am not writing this to prove to you that IoC containers are better or worse, I will try to stay objective. I am going to write some advantages and disadvantages of IoC containers and as you will see many features of IoC frameworks are double edged swords or come with a learning curve.
Note that, for some libraries a container is called something different (like kernel in ninject)
My experience comes from Unity and Ninject using .Net and C#.
Designing around long nested dependency chains
Something I dislike about not using IoC containers is when I end up with long dependency chains. You might have a
Service which needs a
DataRepository which in turn needs a
ConnectionHelper. This would require three classes to be created and injected. Injections within injections can create some long lines of code. Using a container you would just go something like
should happen behind the scenes in your program). This is a huge advantage when it comes to reading the code. You want a service, then you just need to request it. You might be thinking it is not that simple, but I will get to the configuration part later.
If you have a small application this is no issue. If you do not have long dependency chains or if you have a very small application then IoC container may not be for you. In the same sense that any large framework probably does not fit into a small simple application. If the code is decoupled it might not be that big of a deal to introduce IoC containers later.
Where did it come from?
One thing that people who do not like IoC containers say, is that they are "magical". I really do understand this point, which implementation am I currently looking at? where did my implementation come from? I remember the struggle of figuring out what is mapped to which interface and so on when I started out. I cannot say much more than you get used to it. It really comes down to knowing the IoC framework that you use. It also requires you to standardise your code. IoC frameworks have so many features, standardising the way that you set up your projects will help you find dependencies/implementations easier. Convention over Configuration will help here.
Overall you should have your configuration of your container placed in a location where it is easy to find and easy to get an overview of. You might go with the simplicity of always having one interface for one class. In addition to this have all your configurations in a configuration folder/class or sub folders/classes if there is much configuration.
There are so many interfaces!
Often you have a 1:1 with an interface for each dependency. Meaning the
IService is the interface of the
Service. That is right, many classes will have an interface. Some consider this to be "Interface bloat" or too much boilerplate, because the framework you use requires an interface or it simplifies the code to use one. I however found this to make myself write better code along the way. Even though I found all these interfaces silly at first. It also makes it easier for you to create mocks or other test implementations for your tests later (as you already have some nicely defined interfaces).
This also forces you to decide on what the public members of your classes should be. Which is of course also something you can do without an IoC framework, but the frameworks usually create some rules you must adhere to - which in turn can help you write better and more decoupled code.
No build errors
Who does not dread runtime errors? Starting out with IoC containers you will see a ton of those. Your application failed because it was not able to resolve
IService. If you have just joined a team which uses IoC containers and you have no experience with containers, then you might be lost at this. However this may seem more of a regular error to those who are used to IoC containers. The problem here is that you get no compilation error.
Many leave it at that, others create tests for their applications. Your unit tests will not catch this as everything is most likely mocked, but an integration test will catch this. I have even seen teams testing their containers directly.
One of the features I have enjoyed the most about IoC containers is lifetime management. Overly simplify it and it comes down to whether to create a new instance when requested or keep returning the same.
This has some huge caveats. In some frameworks the class itself does not specify whether it is created once or lives and dies with the application. Which makes it hard for the next developer who comes by your code. If this is his or her very first encounter with application lifetime dependencies, then it can end in a disaster. If the class is made a singleton and the developer does not anticipate this you may get all sorts of errors. Suddenly a state within the class is made available all across the application. I would normally anticipate a fresh newly created object when requesting it from the container, unless there is a very good reason not to.
However if you are aware of these lifetime objects then they can save you on the performance side. Whether this is a huge gain depends very much from case to case.
Investing in a third party library
I believe this needs a bullet on its own. Yes you can create your own IoC framework if you wish, however you will not get fixes (or bugs for that matter) for free.
Using a third party library is an investment. You spent time learning that library and getting good at using it, however this also forces everyone else on the project to use the same framework (if you use multiple IoC frameworks it can be quite chaotic). This gives not only you a long learning curve but the entire team on the project.
However using the simplest features of an IoC framework usually does not take long to learn. It can also speed things up, as long as everyone on the project is in on the framework. As frameworks often gives (forces?) you a foundation on how to structure your code
Here I have created a small summary of what i just wrote (tl;dr):
- Forces you to write more modular code
- Decouples the application
- Centralized configuration
- Control over lifetime of dependencies
- Takes care of long nested dependency chains
- Another framework in your toolbox (another mindset to adapt)
- Hard to figure out the flow in the application. As in, what is the actual implementation of the dependency you are looking at?
- No build errors - requires some way of testing dependencies together (yes not unit tests)
- Requires configuration of dependencies.
I hope this gives you an idea of why I see IoC containers as double edged swords and that most of the features come with a cost. This was my advantages and disadvantages of IoC containers. Did I miss anything? please write in the comment section below!