DI - Dependency Injection
Dependency injection is a technique in which an object receives other objects it depends on.
It is actually one form of the broader technique inversion of control.
What is it?
Dependency injection is a technique where every other object (service) an object (client) depends on is injected from the outside. So instead of simply instantiating everything on its own, a class relies on it being passed in from the outside.
You can also view it this way: Imagine your favorite RPG characters could only wear the equipment they started with. No way to change the equipment or the look of your characters' equipment from the outside.
Would be boring and inflexible, wouldn't it?
This is why modern RPGs let you change your character's equipment. And you can compare this to DI. Every slot requires a certain type of equipment. And you can change your character's equipment on the fly, by dropping a new piece into a fitting slot.
The analogy/example is of course one of a mutable object, but I want you to get the general idea of what it is. And it would be pretty bad if you had to kill your character and recreate it every time you wanted to change your main hand weapon, wouldn't it?
What is it good for?
In the strictest form one could possibly think of, DI helps you to respect the single responsibility principle: A class should be good at one thing. But knowing how to do your own job and how to create other objects are two things already!
But it does even more. It helps you to make your code loosely coupled and to make it testable.
DI, in fact, also encourages you to use interfaces instead of specific types as injectables (services). This means that the client only expects a certain interface to be satisfied, which specific implementations can do, and then be used as services. Dependencies become replaceable, and interchangeable, which is also a pretty great benefit of it!
An example issue
To actually get an idea of the benefits, imagine the following example: You have a service-level implementation in one of your APIs to fetch user data, and this implementation depends on a user repository, which handles the database access for you.
You have already made a great first step to put the persistence logic into a repository, and use it within your service, but whenever you want to test your service you need a database. That's bad! And what's even worse: You can't change the way you access your user's data without changing existing code. Not good. If you try to test this code, you'll get a lot of "fun", because you will most likely need at least an in-memory database, even for a trivial unit test.
class UserService {
constructor() {
this.userRepository = new UserRepository();
}
getAllUsers() {
return this.userRepository.findAllUsers();
}
saveUser(user) {
if (!this._isUserValid(user)) {
throw new Error("...");
}
this.userRepository.saveUser(user);
}
findUserById(user) {
this.userRepository.findUserById(user);
}
_isUserValid(user) {
// ...
}
}
Solving the issue
By making only a small change you can decouple your client from its services.
Making this change in a statically typed language would, of course, involve a little more work, as you'd have to create an interface, make your repository implement it, etc.
class UserService {
constructor(userRepository) {
this.userRepository = userRepository;
}
getAllUsers() {
return this.userRepository.findAllUsers();
}
saveUser(user) {
if (!this._isUserValid(user)) {
throw new Error("...");
}
this.userRepository.saveUser(user);
}
findUserById(user) {
this.userRepository.findUserById(user);
}
_isUserValid(user) {
// ...
}
}
After applying this change you have a lot more flexibility. Unit tests can inject a mock that doesn't require any form of database anymore. This is great. And you could now also implement a repository that doesn't use a relational database, but a NoSQL one, e.g.
Before you leave
If you like my content, visit me on Twitter, and perhaps you’ll like what you see!