Injeção de dependência (DI) com NodeJS + Typescript
Tags: javascript, node, typescript, backend
Published: 2023-07-03 08:28:04

Um assunto pouco abordado na comunidade NodeJS/TS, é injeção de dependências em projetos backend.

Tenho a impressão que é uma prática que não avançou tanto nos ambientes backend nodeJS e eu sinceramente não sei o motivo. Talvez esteja relacionado com a baixa quantidade de bibliotecas disponíveis para aplicar injeção em projetos nodeJS, não sei.

Mas meu objetivo com esse post é mostrar um pouco das vantagens dessa prática para a aplicação nodeJS e como ela pode deixar seu projeto escalável, sem abrir mão de organização e claro, testes unitários.

## O que é injeção de dependência

Como o próprio nome sugere, a injeção de dependência é um padrão de desenvolvimento de software onde um componente de software recebe outro componente como dependência.

O objetivo principal da injeção de dependência é remover o acoplamento entre os componentes. Uma classe que receba outra classe por injeção, não sabe como construir a classe injetada, essa construção fica abstraída no "injetor" de dependência. 

Acredito que o entendimento desse conceito ficará mais simples com o código que vamos desenvolver a seguir.

## Bibliotecas DI diponíveis para nodeJS

Encontrei duas bibliotecas que são seguras para qualquer ambiente nodeJS em produção:

* TypeDI - https://github.com/typestack/typedi#typedi
* Awilix - https://github.com/jeffijoe/awilix#awilix

### TypeDI

Essa biblioteca adotou o padrão "decorators" para injetar as dependências nas classes. Particularmente, eu gosto bastante dessa abordagem pois batendo o olho na classe, eu consigo saber que ela está usando DI. Exemplo de injeção usando TypeDI:

```typescript
import Container, { Service } from 'typedi';

import UserRepository, { IUserRepository } from '../../repositories/user';

@Service()
class User {
  private userRepository: IUserRepository;

  constructor() {
    this.userRepository = Container.get(UserRepository);
  }

  public get() {
    return this.userRepository.get();
  }
}

export default GetUser;
```

Apesar de eu gostar mais dessa abordagem que a lib TypeDI propõem, confesso que a documentação deles deixa bastante a desejar. Possui poucos exemplos e algumas páginas estão em branco, sem explicação dos conceitos.

Deixo aqui [o link](https://github.com/lucasprochnow2/DI-with-typedi) de um projetinho node no github que usa o TypeDI para injeção de dependência. Criei esse repositório para entender melhor a lib na prática. 

### Awilix

Tem uma proposta bem parecida com a biblioteca acima, mas ela nos dá mais opções na hora de injetar as dependências dentro de um Container. Veja os exemplos abaixo:

```typescript
import awilix from 'awilix';

// Class
class UserController {
  userService

  constructor(deps) {
    this.userService = deps.userService
  }

  getUser(req) {
    return this.userService.getUser(req.params.id)
  }
}

// Registra a dependência como classe
container.register({
  userController: awilix.asClass(UserController)
})

// Factory function
const makeUserService = ({ db }) => {
  return {
    getUser: id => {
      return db.query(`select * from users where id=${id}`)
    }
  }
}

// Registra a dependência como função
container.register({
  userService: awilix.asFunction(makeUserService)
})
```

Ao contrário da lib TypeDI, a documentação do Awilix é (muito) mais completa, com vários exemplos práticos e explicação de cada conceito. O criador do Awilix é bem ativo no github, sempre respondendo as issues e ajudando o pessoal que usa a lib, o que dá mais pontos para essa lib, na minha opinião.

## Mão na massa

Para exemplificar de forma prática a injeção de dependência em um projeto nodeJS, eu decidi usar a lib Awilix por dois motivos principais:

1. Robustês: Ela possui várias opções para gerenciar o ciclo de vida da injeção e alguns modos de injeção diferentes. Essa variedade de opções é muito importante quando a aplicação começa a escalar e problemas começam a aparecer em produção;

2. Já provei que a lib performa muito bem em produção. Ela roda em um microsserviço node que recebe centenas de milhares de requisições por dia.

O objetivo dessa etapa, não é ser um passo a passo de como criar uma api node com injeção de dependência do zero, mas sim explicar alguns pontos principais, tomadas de decisão que precisei fazer e vantagens da injeção na aplicação como um todo. Obviamente, tudo isso é minha opinião baseado nas experiências que tive. 

Para isso, preparei um projeto que vou usar como referência aqui: https://github.dev/lucasprochnow2/DI-with-awilix

### Chega de papo furado e vamos lá!!

Primeira dúvida que tive ao iniciar o projeto: O `server` vai entrar na injeção ou vou iniciar a aplicação a partir do `server`? (Entenda `server` aqui como um "Express Server" em uma api node).

Minha decisão foi iniciar a aplicação a partir do `server` e só então injetar todas as dependências no `container`, deixando o `server` fora do `container` de injeção. 

Decidi seguir por esse caminho pois, partindo do princípio que essa aplicação é uma api REST, faz mais sentido na minha cabeça iniciarmos a aplicação construindo toda a "fundação" primeiro, que pra mim é o `server` e a partir dessa "fundação" construir o resto da aplicação. 

Veja, você pode entender que a "fundação" dessa aplicação é o container com as dependências. Não está errado, é só outro jeito de "ver o mundo" hahaha.

Veja abaixo como ficou a "fundação" da api node:

[src/index.ts](https://github.dev/lucasprochnow2/DI-with-awilix/blob/c26220f14e87c2995a41f7c85a6a2e92366ffcea/src/index.ts):
```typescript
import ExpressServer from "./core/server";

const expressServer = new ExpressServer();
expressServer.initialize();
```

[src/core/server](https://github.dev/lucasprochnow2/DI-with-awilix/blob/c26220f14e87c2995a41f7c85a6a2e92366ffcea/src/core/server/server.ts):
```typescript
import express, { Application } from "express";
import bodyParser from "body-parser";
import { AwilixContainer } from "awilix";

import RestRouters from "../../rest/routes";
import initializeInjection from "../injection";

const PORT = 3000;

class ExpressServer {
  server: Application;
  container: AwilixContainer;

  constructor() {
    const container = initializeInjection();

    this.server = express();
    this.container = container;
  }

  initializeMiddlewares() {
    this.server.use(bodyParser.json());
    this.server.use(bodyParser.urlencoded({ extended: true }));
  }

  initializeRestRouters() {
    const restRouters = new RestRouters(this.server, this.container);
    restRouters.initialize();
  }

  initialize() {
    this.initializeMiddlewares();
    this.initializeRestRouters();

    try {
      this.server.listen(PORT, (): void => {
        console.log(`Connected successfully on port ${PORT}`);
      });
    } catch (error) {
      console.error("Error occurred: ", error);
    }
  }
}

export default ExpressServer;
```

### Onde acontece a injeção das dependências?

Se você observar o constructor da classe [ExpressServer](https://github.dev/lucasprochnow2/DI-with-awilix/blob/c26220f14e87c2995a41f7c85a6a2e92366ffcea/src/core/server/server.ts), que está representada na sessão anterior, ele possui um `const container = initializeInjection();` que é responsável por inicializar todo o container com as dependências do projeto.

Veja que essa função [initializeInjection()](https://github.dev/lucasprochnow2/DI-with-awilix/blob/c26220f14e87c2995a41f7c85a6a2e92366ffcea/src/core/injection/injection.ts) faz:

```typescript
import { AwilixContainer, createContainer } from "awilix";

import modulesPathList from "./modulesPathList";
import options from "./options";

export default function injection(): AwilixContainer {
  const container = createContainer();

  container.loadModules(modulesPathList, options);

  return container;
}
```

O Awilix permite carregar módulos da aplicação passando uma lista de caminhos relativos dos módulos que você deseja carregar. Veja abaixo o que a variável `modulesPathList` do trecho acima representa:

```typescript
export default [
  "src/domain/services/**/*.ts",
  "src/domain/repositories/**/*.ts",
  "src/rest/routes/**/*.ts",
];
```

Aqui está uma das grandes vantagens do Awilix com relação às outras libs de injeção nodeJS. Com poucas linhas de código, eu consigo ter todos os componentes da minha aplicação injetados no container. 

Outra vantagem dessa abordagem é que tudo que for adicionado dentro desses três caminhos no futuro, será automaticamente injetado no container. Isso evita com que alguma mudança tenha que ser feita no contexto da injeção, caso um novo service for implementado, por exemplo.

### Como eu uso o container com as dependências agora?

Com o container de injeção construído direitinho, agora nós conseguimos usar os módulos que estão no container.

Como exemplo, vou usar a classe do service [user/findAll](https://github.dev/lucasprochnow2/DI-with-awilix/blob/c26220f14e87c2995a41f7c85a6a2e92366ffcea/src/domain/services/user/findAll.ts), mas você pode observar que os outros services são bem parecidos.

```typescript
import { IUserFindAllRepository, User } from "../../repositories/user/findAll";

export interface IUserFindAllService {
  findAll(): User[];
}

type TDeps = {
  findAllUserRepository: IUserFindAllRepository;
};

class FindAll implements IUserFindAllService {
  private findAllUser: IUserFindAllRepository;

  constructor({ findAllUserRepository }: TDeps) {
    this.findAllUser = findAllUserRepository;
  }

  public findAll() {
    return this.findAllUser.findAll();
  }
}

export default FindAll;
```

Bom, para que esse service possa enxergar os módulos que estão no container de dependências, ele também precisa estar dentro do container. Se voltarmos na sessão anterior, nós adicionamos a camada `services` dentro do container através do caminho `"src/domain/services/**/*.ts"`. 

Esse service de exemplo tem por objetivo retornar todos os usuários da fonte de dados e para isso ele precisa perguntar para o repositório de usuários quais são esses usuários. Portanto, o componente que ele precisa acessar do container de dependências é o repositório de usuários.

Por padrão o Awilix expõem os componentes injetados, nos parâmetros dos construtores da classe. Veja que na classe acima, estamos descontruíndo os argumentos do `constructor` e pegando apenas a função `findAllUserRepository`. Essa função foi carregada na sessão anterior, quando declaramos o caminho `"src/domain/repositories/**/*.ts"` para o injetor.

Um ponto muito positivo do Awilix é que ele não se limita a injetar as dependências apenas em componentes de classe. Ele consegue injetar também em funções factory. Veja o exemplo abaixo do [roteador de usuários](https://github.dev/lucasprochnow2/DI-with-awilix/blob/c26220f14e87c2995a41f7c85a6a2e92366ffcea/src/rest/routes/user.ts):

```typescript
import { Router, Request, Response } from "express";
import { IUserFindAllService } from "../../domain/services/user/findAll";
import { IUserFindByIdService } from "../../domain/services/user/findById";

type TDeps = {
  findAllUserService: IUserFindAllService;
  findByIdUserService: IUserFindByIdService;
};

const userRoutes = (deps: TDeps) => {
  const { findAllUserService, findByIdUserService } = deps;
  const router = Router();

  router.get("/", (_: Request, res: Response) => {
    const getUser = findAllUserService.findAll();
    res.status(200).json(getUser);
    return;
  });

  router.get("/:id", (req: Request, res: Response) => {
    const getUser = findByIdUserService.findById(req.params.id);
    res.status(200).json(getUser);
    return;
  });

  return router;
};

export default userRoutes;
```

Repare que o roteador da rota `/user` acima, precisa de duas dependências: `findAllUserService` e `findByIdUserService`.

## Conclusão

Meu objetivo com esse post foi trazer uma ideia e demonstrar quais são as vantagens de usar injeção de dependências em projetos nodeJS. 

O Awilix tem muito mais opções e detalhes para serem explorados. A lib é bem completa e cheia de recursos. Recomendo fortemente a leitura [das docs](https://github.com/jeffijoe/awilix#awilix) antes de começar a usar de fato em qualquer projeto. 

Deixo minha sugestão dos próximos tópicos para vocês estudarem sobre o Awilix. Quem sabe posto outros conteúdos aprofundando nesses tópicos futuramente:

- [Lifetime management](https://github.com/jeffijoe/awilix#lifetime-management);
- [Injection modes](https://github.com/jeffijoe/awilix#injection-modes).

Deixei os links dos projetos que usei de base para esse post, mas vou deixá-los novamente abaixo:

- Repo usando TypeDI: https://github.com/lucasprochnow2/DI-with-typedi
- Repo usando Awilix: https://github.com/lucasprochnow2/DI-with-awilix