Why using Dependency Injection?
One of my concerns, when I start any project is keeping the code highly maintainable. That can be achieved in many ways, and sometimes it can be subjective from person to person, but over the years, some patterns and techniques were well established when it comes to object-oriented programming, and since TypeScript was the chosen language for building GitMarker, I wanted to make it as SOLID as possible.
The idea of using the Dependency Inversion Principle (DIP)
occurred naturally to achieve loose coupling between the types I needed to create. You can do it relying on abstractions rather than concretions. If you want to see SOLID in practice, please check my project where I showcase all the 5 principles. I also wrote an article split into two parts explaining how the project fits with the principles. check it out!
The goal here is not to give you a course of Inversion of Control (IoC) or Dependency Injection (DI), but briefly, Inversion of Control is a design principle
, while Dependency injection a design pattern
and a subset of IoC. There are plenty of frameworks for managing dependencies automatically, and they’re known as IoC containers
.
Fundamentally, IoC containers must provide ways to register, resolve and dispose of types. I searched for widely used IoC containers and could find excellent options, such as:
I’ve chosen InversifyJS for its popularity.
Installing InversifyJS
1
$ npm install inversify reflect-metadata --save
As mentioned on the official documentation, you will need to enable these keys in the tsconfig.json
.
Hello World, Command!
Although TypeScript is a superset
of JavaScript, in a sense, all valid JavaScript is a valid TypeScript code; implementing types, as the name suggests, is the sole purpose the language was for, so I resisted the temptation of keeping exported functions for the commands. Even if I put them as separate and well-organized files, I wanted to stick with the language paradigm.
However, when you bootstrap an extension, you may notice that the only .ts
file you got in addition to the test suite files, is the extension.ts
, which exports the activate
function, and then, the first command was registered to the context. There’s also a required reference of this command in the package.json
manifest file, which I will cover in a further chapter.
Making it classy!
Let’s convert this into a class
, and to do the same for all commands, I created a common interface
named Command defining the command’s Id:string and the execute () function. Consequently, a class implements the interface.
The keyword get
for Id defines a getter
, so you can refer to Id by calling CreateCategory.Id, is pretty similar to C# and other languages. Also notice the @injectable() decorator, imported from 'inversify'
module. It will make sense soon.
Last is creating and exporting the TYPES
object to define some identifiers to Inversify. Since all of them implement the Command interface, you only need the base type for commands.
Containing things
As suggested from the framework’s documentation, I created the inversify.config.ts
file to instantiate and export a container and bind all my @injectable types
Once I had it all set, I needed to replace the code from extension.js
with an automatic command register; otherwise, I would have to register all the commands to the context manually. To ease that, I implemented the service named CommandsManager
. I based this code on this very interesting article explaining some techniques of DI with VS Code extensions, which I used on GitMarker. Thanks to that, I could improve my code a lot.
Finally, the activate() method is now simple and clean. Notice that you can call any registered type from a container as below.
I hope it can inspire you as other blogs have inspired me to seek elegant and optimal solutions like this. See you in the next chapter!
Links worth checking
- Check the InversifyJS’ official documentation
- Check the inversifyJS’ multi-inject documentation
- Check the Extension API to learn about the extension anatomy.