Inversion of Control (IoC) principle using Typescript and InversifyJS [UPDATE May 2022]
As a follow up from my previous article about SOLID principles, I’m writing this one to introduce you to the concept of Inversion of Control (IoC) and an example implementation using InversifyJS and Typescript .
As stated, this article aims to be an introduction to the Inversion of Control principle and to complement this, here’s a recently updated version of my Github repository where you’ll find a basic REST API implemented from scratch and using the technologies that I mention below:
What is Inversion of Control?
Inversion of Control (IoC) is a design principle that aims to invert the control flow of an application, in order to achieve loose coupling between its classes.
This term was popularized by Stefano Mazzocchi in a defunct Apache Software Foundation project, Avalon, then further popularized in 2004 by Robert C. Martin and Martin Fowler.
Normally, our code makes use of a framework and it’s in charge of calling the framework to do what our code wants.
What if we invert this and we tell the framework how to create our classes and how to use them?
We will do this annotating our classes and telling the framework how to create them. Our code will make use of abstractions of those classes and those classes will be resolved in runtime by the framework.
What’s an abstraction?
An abstraction is a general concept or idea, rather than something concrete. Since we’re talking about Object-Oriented Programming, we can think of interfaces as the most basic form of abstraction. By using interfaces to design our classes, we reduce complexity and avoid relying on concrete implementations.
Inversion of Control and Dependency Inversion Principle
Inversion of Control and Dependency Inversion Principle (DIP) are both design principles that aim to achieve loose coupling between classes.
These are two different principles but I relate them because by inverting the control, we’ll be able to comply with the Dependency Inversion Principle.
Dependency Injection
Unlike the IoC and DIP principles, Dependency Injection is a design pattern in which an object receives other objects that it depends on. Those objects are called dependencies.
It allows us to achieve separation of concerns by separating the creation and the use of objects.
We’ll make use of this pattern to achieve Inversion of Control.
Difference between Design Principle and Design Pattern
These two concepts may seem pretty similar but the thing is they are not. While design principles provide high-level guidelines to design software, they do not provide any implementation guidelines or details.
On the other hand, design patterns provide low-level solutions to solve commonly occurring problems. They suggest proven implementations for those problems.
Implementation
As described at the beginning, we’ll use InversifyJS and Typescript in our implementation. InversifyJS is a container that allows us to identify and resolve our dependencies.
In our example, we’ll define a Service class which depends on a Repository class to interact with the database and at the same time, I’ll inject a dummy database to my Repository class (similar to the one on my previous article in the DIP example).
I will focus on my implementation. InversifyJS docs show you how to setup the project.
Now, let’s start working on the example. We’ll start defining our interfaces:
Next, we add an entity class that will describe our posts:
Now we implement our interfaces. As you can see, we’re using the injectable
and inject
decorators to annotate the classes we want to inject and injecting a dependency into a class, respectively.
Configure service identifiers. InversifyJS recommends the usage of Symbols, though we can also use classes and string literals:
Create and configure our container. Here we’ll bind our abstractions to our implementations. After this, we’ll be able to get an object by using its identifier:
Finally, here we have a simple of example on how we can use our container. I’m getting the PostService
using the identifier defined in types.ts
.
As you can see, I’m getting the PostService
and it got a PostRepository
instance passed to its constructor in runtime. Also, the repository class got a MemoryStorage
instance. If you pay attention to our implementations, we always reference abstractions and InversifyJS takes care of resolving our dependencies according to how we defined our bindings.
Conclusion
In the end, we have an example that complies with SOLID principles:
- Single Responsibility Principle (SRP): We have defined classes that take care of one thing only.
- Open/Closed Principle (OCP): Since we are relying on abstractions, we don’t have to make changes on our calling code, if we wish to change an implementation.
- Liskov Substitution Principle (LSP): We can replace any of our objects by another one as long as they implement the same interface.
- Interface Segregation Principle: We created a
PostRepositoryInterface
that extendsRepositoryInterface
, and we’re using that one to bind our implementation. That way, we keep our base interface clean and we can add custom methods on our custom interfaces. - Dependency Inversion Principle: We’re relying on interfaces everywhere. The only place where we’re making use of our classes is on the
container.ts
file, when defining our bindings.
I hope this article gives you a good insight of IoC and its implementation using InversifyJS.
Thanks for reading :)