Dependency injection is one of the basic programming principles that I learned and still remember. It’s a very useful principle but I don’t see it being used widely in the Javascript world. From all the Javascript libraries and frameworks that I have used before, only Angular 1 and Loopback force the use of dependency injection. In this blog post, I am going to discuss dependency injection and why is it important for medium to large projects.

Dependency injection is mentioned as part of the famous SOLID principle. I don’t remember the exact definition but the basic idea is to explicitly define and provide the dependencies of a module. Considering this piece of code

This is called implicit dependency where all the dependencies are statically defined and provided, they can’t be changed under any circumstances. There are some problems with this approach

It’s harder to test. If dep1 is a module that connects to a 3rd party service, we probably don’t want to call that service every time we run test. It’s not nice.

It’s not flexible. Once the dependency is defined, it’s set in stone, we can’t change it anymore. Imagine that we need to use the same module somewhere else, either copy/paste or bundle it as a npm package, the same dependencies need to be installed there.

It’s difficult to know what a module needs and what to expect when we use it. For example, if a module requires a database connection implicitly, the only way that we can know whether using that module changes anything in the database or not is to read its source code. With dependency injection, when we initialize a module, we must explicitly provide the dependencies which act as a reminder about what the module does.

There are some other alternatives such as sinonjs when it comes to mock/stub a module while testing. One can go with implicit dependencies, and use sinon to stub all the methods in dep1 to test doSomething. It totally is a valid approach for testing, in fact I have been using it in a lot of my projects. But as the project grows the same logic to stub a dependency repeats itself and eventually we will end up with the exact same approach where we define a whole new stub module for the dependency. For example, without dependency injection, we can do this with sinon

And if we forget to stub a method of the dependency when we implement a new feature, something bad might happen. For instance, while developing a module, we need to introduce a new call to the 3rd party API through a new method in the dependency. If we forgot to stub the dependency in our tests, everything will still work but we are consuming the API needlessly, in some cases we might even run into a quota limit. On the other hand, if we use dependency injection, and our mocked dependency doesn’t have the new method (there is a way to detect this even before running the code, I will cover it shortly), tests will fail.

As the project grows, we need to scale to multiple processes. At this point, having cached data in the memory is not feasible anymore. Therefore, we need to switch to a different storage (I usually go with Redis). All we need to do now is to implement a different storage based on Redis and replace the memory cache that we have.

Just like with pretty much everything else, dependency injection is no silver bullet. Using it correctly will lead to a clean and flexible architecture while doing it wrong will lead to an unnecessarily complicated architecture. I usually start with something simple to quickly deliver, then while I have a better picture of what I want to build, I will start putting stuff to where they should be.

Dependency injection alone doesn’t make much of a difference in the Javascript world due to the lack of types. However, when used with a static type checking like Flow, it really shines. I personally don’t like Flow that much, their error reporting is always a mystery, but it does the job.