What is Dependency Injection?
Tags: di, spring, dependencyinjection
Published: 2021-04-13 18:58:47

"_Dependency Injection is just passing parameters_", "_Spring is necessary to do Dependency Injection properly_", "_Dependency Injection is a way to escape from functional programming_": these are just some of the quotes circulating about Dependency Injection. As for a technical pattern, it seems quite controversial. Is there any substance in Dependency Injection (DI)? Is this something you should be using in your application? Or maybe using DI makes you a bad programmer?

These are all good questions! However, what's often missing from discussions is an accurate description of what Dependency Injection exactly is. A good, **precise definition** might stop many arguments before they begin.

The [classic article](https://martinfowler.com/articles/injection.html) by Martin Fowler is a good start, however it's quite dated and although quite long, does not give an exact definition. Moreover, our usage of _Object Orientation_ (OO) has evolved since 2004, and is increasingly blended with _Functional Programming_ (FP), hence an update might be in order. Popular answers on [Stack Overflow](https://stackoverflow.com/questions/130794/what-is-dependency-injection) and the [Wikipedia](https://en.wikipedia.org/wiki/Dependency_injection) entry provide reasonable benefits on why to consider using DI in an application, but fail to succinctly capture the main goals of the pattern, or as many critics of DI point out, describe nothing else than passing a parameter to a function.

Let's try to clear things up!
-----------------------------

The classes and objects that we create and use in our applications tend to fall in two categories:

1.  **Service objects**. These are "bags of methods" or "sets of functions", and are used to modularize the code base. Good examples of service objects might be `UserRepository`, `EmailSender`, `ProfileManager`, etc.
2.  **Data objects**, either pure data or data combined with behavior ("proper" OO). Examples include `Product`, `Invoice`, `UserVisitor`, …

Both types of objects are created at different times in the application and used in different ways.

Service objects tend to be created **once** at the **start** of the application. They don't change during the execution of the program. In other words, service objects are the **modules** from which your application logic is composed. Services might **depend** on one another, forming a **dependency graph**. Moreover, specific implementations of services might be **interchangeable**, depending on configuration, environment, command-line options, etc.

The crucial property of the service/module graph is that it is **created statically**. Only when the graph of services is wired, the application is usually ready to serve user requests. Hence the service objects/modules are static and **global**, as well as typically **stateless**. They only contain methods, without any (mutable) data, except maybe for configuration and pre-computed values. There are exceptions as well, such as cache services.

Data objects, on the other hand, are created **dynamically**, either in response to user interaction, API invocation, scheduled tasks, etc. They usually have a short, **local** lifespan. They **carry and manipulate** the data that the application processes. They might combine data and behavior, or be pure, "thin", data structures.

Summing up, we have the following properties of **service** vs **data** objects:

*   _static — dynamic_
*   _global — local_
*   _stateless — stateful_

But we were supposed to talk about dependency injection, where is it then?

> **Dependency Injection is the process of creating the static, stateless graph of service objects, where each service is parametrised by its dependencies.**

That's it! This definition has several implications:

1. **First of all**, a direct consequence is that DI doesn't mandate the use of any framework. It's perfectly feasible to do dependency injection ["by hand", using just constructors](http://di-in-scala.github.io). Whether doing manual dependency injection, [compile-time](https://gitter.im/adamw/macwire) dependency injection, or run-time DI, is an implementation detail.

Frameworks are of course allowed as well. For example, [Spring](http://spring.io) is a DI framework, which uses annotations, xml and reflection to create the static service (component) graph, so it definitely matches the definition. Whether it make sense to sacrifice control over how you create objects and rely on runtime reflection is exchange for a little less code, is another debate.

2. **Secondly**, by requiring that the services are parametrised by their dependencies, we mandate _inversion of control_ (IoC). This makes the dependencies interchangeable (e.g. for testing, or by configuration). Moreover, it's up to the wiring code (the code that creates the static object graph) to decide which specific implementation is used to satisfy a dependency; this responsibility is outside of the control of the service; the dependencies are provided  _externally_  by the DI process.

3. Dependency injection isn't just passing function parameters. Passing function parameters can be used to implement DI, but what matters here is **why** we are passing these parameters in the first place: to create the static graph of modules at the start of the application. Parameter passing is an implementation detail.

4. As I've also [argued before](https://softwaremill.com/reader-monad-constructor-dependency-injection-friend-or-foe/), DI is different than other approaches for managing dependencies, such as the reader monad. The reader monad is a mechanism that is **complementary** to dependency injection, as it makes the dependencies explicit, while with DI the dependency graph is created upfront and hides the dependencies from normal method/function calls. Both mechanisms are useful for different purposes.

5. Finally, DI and FP are orthogonal, and DI is a perfectly valid and useful concept when doing Functional Programming. The functions that are used in FP also often need to be grouped in "modules", which is another way of saying that functions are grouped as methods into classes; they need to be parametrised by other sets of functions (other modules); and they need to be substituted depending on configuration, or in tests.

Nothing in the definition of DI forces us to use mutable data, side-effects, frameworks, impure functions etc.

To sum up, is DI machinery created to avoid FP constructs? No! It can use that machinery, for a clearly stated goal: **creating the (usually static) module graph on application startup**. It's not a mandatory part of every application: it's definitely viable to write good, readable code without it. But as the codebase grows, modularising the code and adding a startup step which wires all of the modules can improve the readability, explorability and maintainability of the project.