Dependency injection is one of the most widespread and beneficial architectural patterns in software development. However, unfortunately, it’s commonly misunderstood and misrepresented.
You’ve probably heard claims like “dependency injection is just passing arguments into constructors”, “dependency injection is a simple concept”, “dependency injection is an overkill for small projects”, etc. In my opinion, all of these claims are misleading “myths” which get repeated over and over, to the point when developers stop questioned their validity.
Therefore, in this post, I’m going to review the most common misconceptions about dependency injection and explain why they are incorrect.
Dependency Injection is Just Passing Arguments Into Classes
I’ll start with the following class:
public class SomeClient { private final SomeService mSomeService; public SomeClient() { mSomeService = new SomeService(); } }
That’s obviously not good because “something about testing” (I’ll address the relation between dependency injection and testability later). To fix this unfortunate implementation, the client, instead of constructing the service by itself, will ask for it as a constructor argument:
public class SomeClient { private final SomeService mSomeService; public SomeClient(SomeService someService) { mSomeService = someService; } }
After this change you can “easily test” and that’s what dependency injection is all about.
The above description is misleading. I can’t claim that it is totally wrong because it does capture part of what dependency injection is, but it’s incomplete to the point of being counter-productive. To understand what I mean by that, consdier the following analogy.
What would you think if software development would be described as “just writing code”? You’d probably realize that while you indeed write code as a developer, this description doesn’t really capture the essence of the process. Analogously, the above explanation of dependency injection is equally misleading.
It won’t take much effort to prove my claim. You just need to consider a bit less trivial case. For example, is it fine to not inject the List in this case:
public class UserAggregator { private final List<User> mUsers; public UserAggregator() { mUsers = new LinkedList<>(); } public void addUser(User user) { mUsers.add(user); } public List<User> getAllUsers() { return mUsers; } }
I think we can all agree that passing the List into UserAggregator ‘s constructor would be weird, but, then, why constructing List in this case is alright, but SomeService in the previous example isn’t?
Here is another example:
public class SomeClient { private final SomeService1 mSomeService1; private final SomeService2 mSomeService2; public SomeClient(ServiceLocator serviceLocator) { mSomeService1 = serviceLocator.get(SomeService1.class); mSomeService2 = serviceLocator.get(SomeService2.class); } }
ServiceLocator is injected into constructor, so everything is kosher, right? In practice, if you know anything about “service locators”, then you’ll immediately realize that there is a catch here. However, if you believe that DI is “just passing arguments into constructors”, you won’t be able to explain why this example is a poor practice.
I can list a bunch of other examples as well, but I think you already get the picture. If you accept the simplistic idea that dependency injection is just passing arguments into constructors, then you won’t be able to analyze and reason about lots of real-world use caes. This definition works only for the most trivial examples and is useless in the real life.
And, lastly, think about this: if all classes in your codebase will receive the services from outside, then no one will be constructing these services. Obviously, this won’t work. Therefore, the construction logic must reside somewhere inside your application. Where is that place? If you believe that dependency injection is just about passing arguments into classes, you won’t be able to even articulate this super-important question properly because it concerns something way outside of the abstraction level of a single class.
So, all in all, dependency injection is clearly not just about passing arguments into constructor and there must be a larger body of theory accompanying the simple mechanics.
Dependency Injection is Simple
Many developers who use dependency injection say that it’s a simple concept. This statement is a myth which is closely related to the aforementioned simplistic and useless definition, but I still want to address it separately.
First of all, you might be tempted to think that if a considerable portion of developers say that something is simple, then it must be the case. No, it’s not.
I believe that anyone who attended higher education institution met lecturers who are enormously good researchers, but can’t explain core concepts to students because they don’t understand which parts of their material are difficult. These lecturers can’t put themselves in students’ shoes and assume that if something is simple to them (lecturers, after years of experience in the field), then it should also be obvious to students.
In my opinion, the same happens in software industry.
Many experienced developers, who might’ve even struggled with dependency injection by themselves, simply take their own experience for granted. In addition, some individuals call really hard things simple to make themselves look smarter (even if just to themselves).
So, it’s not up to experienced developers to decide what’s simple. It’s the experience of newcomers that indicate whether a specific concept is simple or difficult.
To my best knowledge, no one struggles with the concept of passing arguments into classes. You won’t find many articles, books and courses explaining how to do that. You also won’t see heated online discussions on this topic. It’s simple.
On the other hand, in my experience, most developers struggle with the concept of dependency injection. I won’t even restrict it to newcomers because there is plenty of experienced developers who don’t understand and don’t use it.
It goes like this: “dependency injection is when you pass arguments into classes from outside, really simple idea; now you take this monstrous and extremely complex framework and integrate it into your application”. Let’s admit it, the jump from constructor injection to dependency injection frameworks is anything but simple.
The reason why I wanted to address this myth specifically is because it’s extremely harmful in my opinion.
New developers are being told that dependency injection is simple, but, sooner or later, they face its true complexity. The pragmatic ones will simply say “f**k it, I have work to do” and move on. But many others, especially developers who suffer from “impostor syndrome”, can become even more insecure and discouraged by the fact that they can’t understand this “simple” concept.
If you see yourself in any of the above characters, I’ve got good news for you: it’s not your fault. Dependency injection isn’t simple, and if you try to learn it from inappropriate resource, then it can easily take several months to figure it out.
The Best Benefit of Dependency Injection is Testability
I met quite a few developers who don’t use dependency injection arguing that “we don’t need it because we don’t unit test”. There is a very unfortunate implicit misconception underlying that statement. This misconception says that the main benefit (or, even, the only benefit) of dependency injection is easier testing.
Now, I’m a big proponent of both dependency injection and test driven development. There is indeed some interplay between them and it’s generally true that dependency injection allows you to achieve maximal testability. However, this doesn’t imply that testability is the main reason you’d want to implement dependency injection in your project.
The two main benefits of proper dependency injection is faster development and easier long-term maintainability. And I bet you’ll want to claim these two benefits regardless of whether you unit test your code or not.
Dependency Injection is Just About Using Frameworks
Another common misconception equates dependency injection with dependency injection frameworks. This equivalence is so common that whenever I ask developers whether they use dependency injection and get a positive answer, I can be almost 100% certain that they mean that they use some dependency injection framework.
If you take a moment and think about it, this misconception is completely incompatible with the wrong definition of dependency injection as just passing arguments from outside. At the end of a day, to some extent, we all pass arguments into constructors, methods and fields in our projects. Therefore, if dependency injection would be just about that, then all developers would be able to state that they use dependency injection. However, it’s not the case and in most cases developers say that they use dependency injection only if they use dependency injection framework.
Since these two misconceptions are incompatible, rational developers shouldn’t be able to hold them together, right? Well, that’s not the case and many developers will indeed believe in both these myths. Yet another manifestation of our ability to resolve cognitive dissonance “on the fly”.
The truth is that frameworks aren’t required to implement dependency injection and you can implement perfectly valid dependency injection without any third-party dependencies. This approach is called “Pure dependency injection” (aka “Poor man’s dependency injection”). Furthermore, even if you use a framework, the fundamental theory of dependency injection still applies. If you don’t know this theory, you can end up with bad implementation of dependency injection, or without dependency injection at all.
Said all that, I want to make it clear that I’m not advocating for Pure dependency injection. In most cases, in my opinion, you’ll be much better off using a mature framework. Especially if you don’t have much prior experience with dependency injection.
Dependency Injection is an Overkill on Small Projects
This myth is so wrong that it drives me crazy. Let’s debunk it once and for all.
First, notice that this myth is incompatible with the myths stating that dependency injection is just passing arguments into classes and that it’s a simple concept. After all, if it’s so simple, why not use it in all projects?
On the other hand, if you believe that dependency injection is deep and complicated concept (which is the case), then this myth might actually sound legit. Maybe dependency injection is indeed too complex for small projects and should be considered only after the codebase reaches some threshold size.
The correct approach in this situation is to evaluate pros and cons quantitatively. In other words, you need to perform return on investment (ROI) analysis.
The return part is quite straightforward: faster development, easier long-term maintainability and better testability. For the entire lifetime of the codebase! There are probably other smaller benefits that I forgot to mention, but these three should be major enough to actually make you very interested in dependency injection on projects of any size.
What’s the investment then? Well, it depends on your experience.
I can set up proper dependency injection on a greenfield Spring or Android project in less than one hour. Yes, that’s all it takes an experienced software developer to either implement pure dependency injection scaffold or integrate some third-party dependency injection framework into an empty codebase.
Some developers find it hard to believe that it can be done in such a short time. I can prove it. In this video tutorial I show the approach that I use to integrate Dagger 2 (arguably the most complex dependency injection framework ever) into my Android apps. Even with all the explanations, it’s still under 30 minutes in length.
Given how quickly I can set up dependency injection, there is simply no way I will ever start a new project without it. It would be simply stupid on my part. Therefore, once you understand dependency injection and get hands-on experience with it, you always use it. Even on the smallest projects.
However, if you know nothing about dependency injection, then the situation becomes a bit more nuanced.
If you need to ship the product in two weeks, starting learning dependency injection is probably a bad idea. However, if you know that you’re starting a serious project and can allocate, say, a week, to invest into its infrastructure to ensure long term success, then implementation of dependency injection will probably be your best bet.
And there is one additional critical consideration that you should take into account.
If you decide to start a project without dependency injection, introducing it at a later stage might be either extremely difficult, or even practically impossible. The reason being that if you already have a considerable codebase and want to start using dependency injection, you might be looking at a huge refactoring project that will last months. And you’ll probably want to bring in someone who has proven experience with dependency injection and refactoring because such a refactoring is extremely difficult.
In other words, if you don’t start with dependency injection from the onset, then there is a high chance that you’re dooming your project to never claim its benefits. Think about this point seriously.
So, all in all, there is nothing about dependency injection that makes it an overkill on small projects. It’s extremely beneficial on projects of any size, and the only question you should ask yourself is: should I make the initial investment, or should I take the long term risk?
In my experience, in absolute majority of the cases the initial investment into dependency injection is the way to go.
What’s Dependency Injection Then
After debunking all these myths, you probably expect me to explain what dependency injection really is. It’s a reasonable expectation.
Consider the following diagram:
This diagram schematically shows some codebase in which all the classes are being segregated into two disjoint sets: Functional Set and Construction Set. These sets of classes integrate at very specific points using an explicit pre-defined scheme.
Functional set contains the classes that encapsulate application’s core functionality. Whatever your application does, all of that is defined in classes that belong to Functional set.
Construction set contains the classes that construct and wire together classes from Functional set. There is no application related functionality in the classes of Construction set.
Fundamentally, that’s what dependency injection is.
Note that any concept that segregates application’s logic in such a way “lives” at the level of abstraction of the entire application. It’s an architectural pattern. So, dependency injection is an architectural pattern that embodies Separation of Concerns principle by separating construction and functional concerns inside the application.
If this sounds a bit complicated then it’s only because it is indeed complicated. As I already said, dependency injection is deep and complex concept that can’t be summarized in one paragraph.
Since this post is already long enough, I wouldn’t like to go into more details here, but you can read my article about dependency injection in Android if you’d like to explore this topic further. Even though that article concerns Android, more than 80% of it is explanation of the universal concepts of dependency injection which apply in any object oriented language or framework.
Conclusion
Alright, let’s wrap up.
We debunked some common myths about dependency injection here and I hope that you found this article interesting and thought provoking.
At this point in my professional career, I truly believe that dependency injection is the most fundamental and beneficial architectural pattern in object-oriented design. Yep, the most fundamental and the most beneficial. Therefore, if after reading this article you’ll consider looking into dependency injection more closely, I’ll call it a success for both of us.
As usual, leave your comments and questions below and consider subscribing to the newsletter if you liked this post.
Thanks for this wonderful post. I believed in all of these myths you just debunked.
It’s huge learning in just 10 minutes!
Thanks man
Great article, picked up a lot.