Logo

dev-resources.site

for different kinds of informations.

NodeJs - Dependency injection, make it easy

Published at
8/29/2021
Categories
node
typescript
dependency
injection
Author
vickodev
Author
8 person written this
vickodev
open
NodeJs - Dependency injection, make it easy

If when you are working with #NodeJs and #TypeScript you have not experienced mutation problems is a sign that you are doing things right or maybe you do not know what you are doing and in the latter case you have been playing with luck, good luck of course.

Based on the previous premise it becomes necessary a correct handling of the import of the modules that your application will use to create the pipeline where the requests will enter your application, and for this there are three ways, or at least I know three ways and each one brings with it its implications.

We have the following options:

  1. Import module as a default instance (Singleton)
  2. Import the types and create instances (transient) in the context where they will be used, usually a PrimaryAdapter or in simple words the entry point of that execution.
  3. Create an IoC (Inversion of control) as a Dependency Container Service.

Option 1:

It is the simplest way to do it, but it is the least indicated because if you do not make a good implementation, you will probably have Mutations problems, a headache when working with JavaScript.

In some directory you will have something like this:

import { awesomeProvider, amazingProvider } from "../../providers/container";
import { AwesomeUseCase } from "../../../../application/modules/oneModule/useCases/awesome";
import { AmazingUseCase } from "../../../../application/modules/twoModule/useCases/amazing";

const awesomeUseCase = new AwesomeUseCase(awesomeProvider);
const amazingUseCase = new AmazingUseCase(amazingProvider);

export { awesomeUseCase, amazingUseCase };
Enter fullscreen mode Exit fullscreen mode

And in your controller some like as follows:

import BaseController, { Request, Response, NextFunction } from "../base/Base.controller";
import { amazingUseCase, awesomeUseCase } from "./container/index";

class YourController extends BaseController {
  constructor() {
    super();
    this.initializeRoutes();
  }

  amazing = async (req: Request, res: Response, next: NextFunction): Promise<void> => {
    try {
      this.handleResult(res, await amazingUseCase.execute());
    } catch (error) {
      return next(error);
    }
  };

  awesome = async (req: Request, res: Response, next: NextFunction): Promise<void> => {
    try {
      this.handleResult(res, await awesomeUseCase.execute());
    } catch (error) {
      return next(error);
    }
  };

  protected initializeRoutes(): void {
    this.router.get("v1/amazing", this.amazing);
    this.router.get("v1/awesome", this.awesome);
  }
}

export default new YourController();
Enter fullscreen mode Exit fullscreen mode

The problem with the previous implementation is that if the modules you export have global variables and in the case that two concurrent requests arrive at the same entry point, you will most likely have mutations, why?, because that's how JavaScript works.

Option 2:

This way is the most indicated, but you would dirty a lot your Adapter or entry point (Controller) with imports of all kinds, because most likely your module requires the injection of other dependencies and you would have to do a management of those instances, something cumbersome, and yes, I know you are probably thinking that you would create an index file and there you would do all that heavy work for the resources of the main instance, but it is still dirty, let's see:

import BaseController, { Request, Response, NextFunction } from "../base/Base.controller";
import { awesomeProvider, amazingProvider } from "../providers/container";
import { AwesomeUseCase } from "../../../application/modules/oneModule/useCases/awesome";
import { AmazingUseCase } from "../../../application/modules/twoModule/useCases/amazing";

class YourController extends BaseController {
  constructor() {
    super();
    this.initializeRoutes();
  }

  amazing = async (req: Request, res: Response, next: NextFunction): Promise<void> => {
    try {
      const amazingUseCase = new AmazingUseCase(amazingProvider);
      this.handleResult(res, await amazingUseCase.execute());
    } catch (error) {
      return next(error);
    }
  };

/* other entry points */

  protected initializeRoutes(): void {
    this.router.get("v1/amazing", this.amazing);
    this.router.get("v1/awesome", this.awesome);
  }
}

export default new YourController();
Enter fullscreen mode Exit fullscreen mode

And depending on the amount of modules you use, this can grow in ways you can't even imagine, and that's where I'm going with the next point I want to share with you.

Option 3:

This way is one of the ContainerPatterns and basically is a Container like the IoC services or frameworks but more versatile, since you can handle from inverted dependencies or concrete classes that don't have defined contracts, so without further ado (Shit) let's go to the code.

Everything starts from a Class called Container
and a contract as a dictionary of type IContainerDictionary where we will relate our dependencies, whether Class with or without contracts (Interface) defined, and as we can see we have a get method that receives a type which will serve to manage it.

import { ApplicationError } from "../../application/shared/errors/ApplicationError";
import resources, { resourceKeys } from "../../application/shared/locals/messages";
import applicationStatus from "../../application/shared/status/applicationStatus";

export class Container {
  constructor(private readonly container: IContainerDictionary) {}

  get<T>(className: string): T {
    if (!this.container[className]) {
      throw new ApplicationError(
        resources.getWithParams(resourceKeys.DEPENDENCY_NOT_FOUNT, { className }),
        applicationStatus.INTERNAL_ERROR,
      );
    }

    return this.container[className]() as T;
  }
}

export interface IContainerDictionary {
  [className: string]: NewableFunction;
}
Enter fullscreen mode Exit fullscreen mode

The Container Class should be part of the Adapters Layer of your solution, it should have nothing to do neither with the Application and/or Domain layer of your solution speaking in terms of a Clean Architecture based solution, even in a N Layers.

To use this pattern we go to our Adapters layer, where is our entry point, usually a Controller, there we create a directory called container and in this a file index, and there we would have something like the following code:

import { Container, IContainerDictionary } from "../../../../dic/Container";
import { AwesomeUseCase } from "../../../../application/modules/one/useCases/awesome";
import { AmazingUseCase } from "../../../../application/modules/two/useCases/amazing";
import { awesomeProvider, amazingProvider } from "../../../providers/container/index";

const dictionary: IContainerDictionary = {};
dictionary[AwesomeUseCase.name] = () => new AwesomeUseCase(awesomeProvider);
dictionary[AmazingUseCase.name] = () => new AmazingUseCase(amazingProvider);

export { AwesomeUseCase, AmazingUseCase };
export default new Container(dictionary);
Enter fullscreen mode Exit fullscreen mode

And once we have our container then we can make use of it in our controllers as follows:

import BaseController, { Request, Response, NextFunction } from "../base/Base.controller";
import container, { AmazingUseCase, AwesomeUseCase } from "./container/index";

class YourController extends BaseController {
  constructor() {
    super();
    this.initializeRoutes();
  }

  amazing = async (req: Request, res: Response, next: NextFunction): Promise<void> => {
    try {
      this.handleResult(res, await container.get<AmazingUseCase>(AmazingUseCase.name).execute());
    } catch (error) {
      return next(error);
    }
  };

  awesome = async (req: Request, res: Response, next: NextFunction): Promise<void> => {
    try {
      this.handleResult(res, await container.get<AwesomeUseCase>(AwesomeUseCase.name).execute());
    } catch (error) {
      return next(error);
    }
  };

  protected initializeRoutes(): void {
    this.router.get("v1/amazing", this.amazing);
    this.router.get("v1/awesome", this.awesome);
  }
}

export default new YourController();
Enter fullscreen mode Exit fullscreen mode

Now, how this works:
The key to everything is in the anonymous functions that we create in the dependencies dictionary, since when the get method of the Container is invoked, what we do is to execute this anonymous function so that it returns us the corresponding new instance (transient), something simple but powerful because it also optimizes the memory management of our applications, since there are no instances of any type of dependencies until the moment a request enters our entry point, and once the request has finished, the resources will be released because the execution context in the Call stack will have ended.

It's worth noting that there are packages that do this, some of them are inversify, awilix, typedi, among others NPM packages and now we have this feature as a NPM package in the next link:
dic-tsk

This article is the explanation of a thread I launched a few days ago on twitter (@vickodev) Spanish-tw-thread and while I was writing them it occurred to me that we can increase the possibilities of the container but that will be in another possible post.

I hope the reading of this post has been enriching for the continuous learning path that requires to be dev. :)

injection Article's
26 articles in total
Favicon
Ore: Advanced Dependency Injection Package for Go
Favicon
Beauty Injection: Achieve Radiance Without Surgery
Favicon
Exploring Fat Burning Injections in Mt. Juliet, TN
Favicon
Dependency Injection Explained in C#
Favicon
Bug SQL Injection - Sub bawaslu.go.id - Badan Pengawas Pemilihan Umum
Favicon
Custom Plastic Molding Near Me, Mold Makers, Mold Manufacturers, and Rapid Prototyping: A Comprehensive Guide
Favicon
Loose Coupling and Dependency Injection (DI) principle
Favicon
Dependency Injection for JavaScript Developers
Favicon
AWS Security Stories #04.4: OWASP - Injection
Favicon
Angular Dependency Injection
Favicon
How to create your own dependency injection framework in Java
Favicon
NodeJs - Dependency injection, make it easy
Favicon
A Step by Step Guide to ASP.NET Core Dependency Injection
Favicon
Ծրագրային անվտանգություն՝ SQL Injection (մաս 1)
Favicon
How to implement Dependency Injection in Node.js with Example
Favicon
Why you should use dependency injection
Favicon
Handling Injection Attacks in Java
Favicon
A straightforward introduction to Dependency Injection
Favicon
How to create your own dependency injection framework in Java
Favicon
Let's stop being stupid about security
Favicon
Dagger with a Hilt
Favicon
Security: Released npm package 📦 for Protecting CSV Injection 🚀
Favicon
Yet another dependency injection library
Favicon
O11ycon Talks: Dr. Peter Alvaro, "Twilight Of The Experts"
Favicon
Jersey Injection Dependency example with HK2
Favicon
How to Convert date format to PHP with MYSQLI, using secure queries to avoid SQL injection

Featured ones: