On frameworks and libraries
A logging framework helps you log what’s happening in your application. A UI framework helps you render and animate UIs to the user. A communication library helps connecting parts of a distributed system.
All of these tasks and concepts are pretty easy to understand. They are quite down to earth, and therefore, at a high level at least, easy to explain. They produce tangible, additive difference in your application.
Also the code of your application changes in order to use those frameworks and libraries. You add a logging framework, you write some code against its API and you end up with a file on your disk, or some text in your console. You add a communication library, inherit some base classes, implement some interfaces to designate communication endpoints of your application and you can see two processes exchanging information. I hope you get the picture and I don’t need do go on describing the tangible, additive and visible results of using a UI framework.
What about IoC container?
So what about inversion of control containers? There’s a lot of confusion around what they do, and why you should use one at all. Every now and then I meet a developer, who says they read all the definitions, introductions and basic examples, and they still don’t get why would they use a container. Other times I’m having discussions that can be summarised as (slightly exaggerated for dramatic effect):
I got one of the IoC containers, put it in my application, and then all hell broke loose. We got memory leaks, things weren’t appearing where they should, users were seing other users’ data, we had to recycle the pool every hour because we were running out of database connections, and the app got very slow. Ah and also it destroyed maintainability of our code, because we never knew when something is safe to refactor, since we never knew where and who would try to get them from the container.
Let’s ignore the details for now and concentrate on the wider sentiment.
So? Should I use it or not?
The sentiment is one of confusion, scepticism and frustration. Was it a good idea to use the container? Should we have not used it? Would I use it if I was leading the team?
Truth is, those aren’t necessarily the right questions to ask. You can’t answer them in a vacuum. Should I use my phone to twitter when I’m on the bus? While the answer might be “sure, why not” if you’re a passenger, it would be unequivocally “don’t even think about reaching for your phone” if you’re the driver.
I have seen applications where introducing a container immediately, would only worsen things. I also haven’t worked with any .NET application where, if architected the way I like to do things, the container wouldn’t be beneficial in the long term.
It all boils down to the fact that container solves certain problems that arise if and when you build your applications a certain way, and unless you do, you’re not going to see the benefits it brings, simply because the problems container solves will not be the main problems you’ll be facing, or will not appear in your application at all.
What sort of architecture are we talking about?
Container has certain requirements in order to work smoothly. First and foremost it requires structure and order. At a mechanical level, container takes over certain level of control over low level, mundane details of your application’s infrastructure. It is however still a tool, and unless the environment where it operates is clean, with well defined limited number of abstractions it will struggle, and so will you, trying to get it to work.
There’s a lot of content in the sentence above so let’s dig a bit deeper.
The assumption containers are making is, that you do have abstractions. Getting a little bit ahead of ourselves, one of the main reason for using the container is to maintain lose coupling in our code. The way we achieve this is by constructing our types in such a way that they do not depend directly on other concrete classes. Regardless if it’s a high level type, or low level type, instead of depending on one another directly, container expects they will depend on abstractions.
Another, related (pre)assumption is you will build your classes to expose abstractions. And not just in any way, but keep them small and crisp (structure and order, remember). To maintain loose coupling, the abstractions are expected to provide just a single small task, and most classes will provide just a single abstraction.
It is not uncommon for a concrete class to provide more than one abstraction (for example to bridge two parts of the system) but in that case the container assumes you will separate these two responsibilities into two abstractions.
Large number of abstractions in your system will be reusable, that is a single abstraction will be exposed by more than one type. In cases like that, the container assumes all the implementations will obey the contract of the abstraction, that is they will all behave in a way that will not be surprising to anyone consuming the abstraction without knowing what concrete implementation is behind it.
The point above is especially important to provide class level extensibility. Certain types in your system will depend on a collection of dependencies, and you should not have to go back and change them, whenever you add a new type ending up in that collection. The assumption is, the code using the abstractions, as well as the abstractions themselves are built in such a way, that the type can be extended by swapping (or adding new) dependencies it has, without the type itself having to change.
That’s a lot of assumptions, isn’t it?
You may say the container makes quite a number of assumptions (or requirements!) about how your application is structured. You wouldn’t be wrong.
However if you look up and read the previous section again, forgetting that we are talking about container, does this feel like an architecture you should be striving for anyway? Does it feel like good, maintainable object oriented architecture? If it does, then that’s good, because that’s intentional.
A container doesn’t put any artificial or arbitrary cumbersome constraints onto how you should build your application. It doesn’t ask you to compromise any good practices aiming at maximising testability, maintainability and readability of your code and architecture. On the contrary, it encourages and rewards you for that.
It’s the inversion!
Finally we’re at a point where we can talk about some answers. The container makes a lot of assumptions about how your code is structured for two reasons.
That makes the container different from logging framework or communication framework. None of them cares about how your code is structured. As long as you implement correct interfaces or inherit correct base classes, and are able to call the right API they don’t care.
Inversion of control containers are fundamentally different. They do not require your code to implement or inherit anything and there’s no API to call (outside of single place called composition root). Just by looking at code of your application, you can’t tell if there is a container involved or not.
That’s the inversion of control working. This is what confuses so many people when they first start using containers. The difference is so fundamental from most other frameworks and libraries that it may be hard to comprehend and make the switch.
Inversion of control confuses people and makes it harder to see the value a container brings because the value is not tangible and it is not additive.
Contrast that with the examples from the first section. With logging framework you can point a finger at a piece of code and say “There, here’s the logging framework doing its thing”. You can point a finger at your screen, entry in Windows event log, or a file on disk and say “There, here’s the result of logging framework doing its thing”.
Inversion of control container is just “out there”. You can’t see the work that it’s doing by inspecting your code. That ephemeralness and intangibility is what confuses many new users and makes it harder for them to see the value the container brings to the table.
To make things even slightly more confusing for newcomers, when you’re using a container you don’t end up writing code to use the container (again, expect for a little bit of code to wire it up). You end up writing less code as a result of using the container, not more, which again is harder to measure and appreciate than code you can clearly see as a result of using other kinds of frameworks.
What isn’t there…
The second reason why container makes all of these assumptions about your code is very simple. Unless your architecture follows these rules, you simply will not have the problems that the containers are built to solve. And even if you do, they will be far from being your main concern.
Building your application according to the assumptions that the container is built upon (I hope by now you figured out that they are just SOLID principles of good object oriented C# code) your application will be composed of types exposing certain characteristics.
Following Single Responsibility Principle will lead you down the road of having plenty of small classes.
Open Close Principle will lead you down the way of encapsulating the core logic of each type in it, and then delegating all other work to its dependencies, causing most of those small classes to have multiple dependencies.
Interface Segregation Principle will cause you to abstract most of those big number of classes by exposing an interface or an abstract class, further multiplying the number of types in your application.
Liskov Substitution Principle will force you to clearly shape your abstractions so that they can be used in a uniform way.
Finally, if you follow Dependency Inversion Principle your types will not only expose abstractions themselves, but also depend on abstractions rather than concrete types.
Overall you will have a big number of small classes working with a number of abstractions. Each of those classes will be concentrated on its own job and largely ambivalent (and abstracted from) about who is using it, and details of who it is using.
This leaves you with a problem of putting together a group of those small types (let’s call them Components) in order to support a full, real life, end to end scenario. Not only will you have to put them together, but also, maintain and manage them throughout the lifetime of your application.
Each component will be slightly different. For some of them you will want to have just a single instance, that is reused everywhere, for others, you’ll want to provide a new instance each time one is needed, or may be reuse instances within the scope of a web request, or any other arbitrary scope.
Some of them will have decommission logic, like Dispose method. Some of them will have ambient steps associated with their lifecycle, like “subscribe me to certain events in event aggregator when I’m created and them unsubscribe me when I’m about to be destroyed”.
Also thanks to the power of abstractions and guarantees put in place by following Liskov Substitution Principle you’ll want to compose some of the components using a further set of design patterns. You’ll want to create Decorators, Chains of Responsibility, Composites etc.
Not to mention the fact that in addition to depending on abstractions exposed by other components (let’s call them Services) your components may also have some configuration, or other “primitive” dependencies (like connection strings).
I hope you can see that the complexity of it all can quickly grow immensely. If you want to maintain loose coupling between your components, you will have to write a lot of code to put them together and maintain all the aforementioned qualities. The code to do that can quickly grow as well, both in size and complexity.
Container to save the day
Inversion of control containers exist to replace that code. I know it took me a while to get here, and I did it in a very roundabout way, but there you have it – that’s what IoC containers do – they help you stay sane, and reduce friction of development when adhering to good Object Oriented Design principles in language like C#.
I know it’s not a sexy and concise definition, and some of you will probably need to re-read this post for it to “click”. Don’t worry. It didn’t click for me at first either. The value and goal of IoC container is abstract and intangible, and the nomenclature doesn’t help either.
Some people feel that a lot of misconceptions and misunderstanding about containers comes from their very name – inversion of control container. If you just hear someone say those words, and leave it to your imagination, you’ll most likely see… blank. Nothing. It’s so abstract, that unless you know what it means, it’s pretty much meaningless.
To alleviate that some people started referring to containers as Dependency Injection container. At first this sounds like a better option. We pretty much understand what dependency in context of relation between two objects is. Injection is also something that you can have a lot associations with, software related or otherwise. Sounds simpler, more down-to-earth, doesn’t it?
It does sound like a better option, but in my experience it ends being a medicine that’s worse than the disease. While it doesn’t leave you with blank stare, it projects a wrong image of what a container does. By using the word injection it emphasises the process of constructing the graph of dependencies, ignoring everything else the container does, especially the fact that it manages the whole lifetime of the object, not just its construction. As a result of that, in my experience, people who think about containers in terms of DI end up misusing the container and facing problems that arise from that.
That’s why, even though I’m quite aware Inversion of Control Container is a poor name, that doesn’t help much in understanding those tools, at least it doesn’t end up causing people to misunderstand them.
So there you have it. I admire your persistence if you made it that far. I hope this post will help people undersand what a container does, and why putting it in an application that’s not architected for it, may end up causing more problems than benefits.
I hope it made it clearer what having a container in a well designed application brings to the table. In fact I only spoke about the most basic and general usages scenario that every container supports. Beyond that particular containers have additional, sometimes very powerful features that will add additional benefit to your development process.
So even if you feel you don’t have a need for a container right now, having the problems that the container is meant to solve is often a sign of a good, object oriented architecture, and those are good problems to have.