Inversion of Control (IoC) is a design pattern that allows for loose coupling and a more modular approach to coding. It involves inverting the traditional flow of control between the different components of a program. Instead of classes or modules directly creating or calling other modules or classes, they rely on an external “entity” (it could be a framework, module or even just a main entry point) to manage their dependencies.
In JavaScript/Typescript world, the most commonly used way for implementing IoC is Dependency Injection (DI). DI is a technique that allows a component to specify its dependencies, which are then injected into the component externally. This way, the component is decoupled from its dependencies, making it more reusable and easier to test.
Also this concept could sound familiar to whoever have heard about SOLID’s principles, there is one special Dependency Inversion Principle (DIP) which satisfies the following:
High-level modules should not depend on low-level modules. Both should depend on abstractions. Abstractions should not depend on details. Details should depend on abstractions.
Let’s see a very easy practical example of how this principle can be applied when building API’s using Node:
First of let’s see the following example using Javascript, as we don’t have to deal with type checking the most simple way I can think of for injecting a dependency might be:
1 | class UserService { |
Then when testing UserService in isolation you just need to pass down an object with an implemented find
property and build your expectations accordingly. e.g:
1 | it('user service happy path', async () => { |
But the story is somewhat different when dealing with Typescript and type checking…
1 | export class UserService { |
Now trying to test a Typescript class as before we would see an error when trying to create an
UserService
instance passing downfakeUserRepository
becauseUserService
needs a fullUserRepository
implementation.
Then is when Typescript Interfaces come to the rescue and with a very little tweak defining UserService
class, the ability to use fakes become easy again.
1 |
|
Now fakeUserRepository
just needs to satify the interface IUserRepository
for being a valid UserService
dependency.
1 | it('user service happy path', async () => { |
Here there is a fully implementation in express of what’s described here, this can be used as a playground, bear in mind that this is just a simple example of how to wrap your head around using dependency injection in typescript. Applying this concept to production services might require relying on third parties like InversifyJS for applying IoC effectively.
Happy coding!