Um Guia sobre Injeção e Inversão de Dependências em Node.js e TypeScript

  1. Componentes e composição de software
  2. Como NÃO conectar componentes
  3. Como e por que injetar dependências usando injeção de dependência
  4. Como aplicar Inversão de Dependência e escrever código testável
  5. Considerações sobre containers de inversão de controle

Terminologia

Vamos ter certeza de que entendemos a terminologia sobre como conectar dependências antes de continuar.

Componentes

Vou usar muito o termo componente. Esse termo pode afetar o React.js ou desenvolvedores Angular, mas pode ser usado além do escopo da web, Angular ou React.

Injeção de dependência

Eventualmente, precisaremos conectar os componentes de alguma forma. Vejamos uma maneira trivial (e não ideal) de conectar dois componentes.

// repos/userRepo.ts/**
* @class UserRepo
* @desc Responsável por buscar usuários no banco de dados.
**/
export class UserRepo {
constructor() {}
getUsers(): Promise<User[]> {
// Usamos Sequelize ou TypeORM para recuperar
// os usuários de do banco de dados
}
}
// controllers/userController.tsimport { UserRepo } from "../repos"; // #1 Prática Ruim/**
* @class UserController
* @desc Responsável por lidar com solicitações de API para a rota /user
**/
class UserController {
private userRepo: UserRepo;
constructor() {
this.userRepo = new UserRepo(); // #2 Prática Ruim, continue lendo para ver o porquê
}
async handleGetUsers(req, res): Promise<void> {
const users = await this.userRepo.getUsers();
return res.status(200).json({ users });
}
}
// controllers/userController.tsimport { UserRepo } from "../repos"; // Ainda é uma prática ruim/**
* @class UserController
* @desc Responsável por lidar com solicitações de API para a rota /user
**/
class UserController {
private userRepo: UserRepo;
constructor(userRepo: UserRepo) {
this.userRepo = userRepo; // Muito Melhor, injetamos a dependência através do construtor
}
async handleGetUsers(req, res): Promise<void> {
const users = await this.userRepo.getUsers();
return res.status(200).json({ users });
}
}
// controllers/userRepo.spec.tslet userController: UserController;beforeEach(() => {
userController = new UserController(
new UserRepo() // Deixará os testes lentos porque ele conecta ao banco de dados
);
});

Inversão de Dependência

Inversão de dependência é uma técnica que nos permite desacoplar componentes uns dos outros. Veja isso.

// repos/userRepo.ts/**
* @interface IUserRepo
* @desc Responsável por buscar usuários no banco de dados.
**/
export interface IUserRepo { // Exportado
getUsers (): Promise<User[]>
}
class UserRepo implements IUserRepo { // Não é exportado
constructor () {}
getUsers (): Promise<User[]> {
...
}
}
// controllers/userController.tsimport { IUserRepo } from "../repos"; // Muito Melhor!/**
* @class UserController
* @desc Responsável por lidar com solicitações de API para a rota /user
**/
class UserController {
private userRepo: IUserRepo; // Mudados Aqui
constructor(userRepo: IUserRepo) {
this.userRepo = userRepo; // E Aqui Também
}
async handleGetUsers(req, res): Promise<void> {
const users = await this.userRepo.getUsers();
return res.status(200).json({ users });
}
}

Usando um MockUserRepo para fazer o mock no UserController

// repos/mocks/mockUserRepo.tsimport { IUserRepo } from "../repos";class MockUserRepo implements IUserRepo {
private users: User[] = [];
constructor() {} async getUsers(): Promise<User[]> {
return this.users;
}
}
// controllers/userRepo.spec.tsimport { MockUserRepo } from "../repos/mock/mockUserRepo";let userController: UserController;const mockResponse = () => {
const res = {};
res.status = jest.fn().mockReturnValue(res);
res.json = jest.fn().mockReturnValue(res);
return res;
};
beforeEach(() => {
userController = new UserController(
new MockUserRepo() // Super Rapído! E válido, já que implementa IUserRepo.
);
});
test("Should 200 with an empty array of users", async () => {
let res = mockResponse();
await userController.handleGetUsers(null, res);
expect(res.status).toHaveBeenCalledWith(200);
expect(res.json).toHaveBeenCalledWith({ users: [] });
});

As principais vantagens de DI

Essa separação não apenas torna seu código testável, mas também melhora as seguintes características de seu código:

  1. Testabilidade: Podemos substituir componentes pesados de infraestrutura por componentes fictícios durante o teste.
  2. Substituibilidade: Se programarmos em uma interface, habilitamos uma arquitetura de plug-and-play que adere ao Princípio de Substituição de Liskov, o que torna incrivelmente fácil trocar componentes válidos e programar em código que ainda não existe. Como a interface define a forma da dependência, tudo o que precisamos fazer para substituir a dependência atual é criar uma nova que siga o contrato definido pela interface. Veja este artigo para se aprofundar nisso.
  3. Flexibilidade: Seguindo o Princípio de Aberto e Fechado, um sistema deve ser aberto para extensão, mas fechado para modificação. Isso significa que se quisermos estender o sistema, precisamos apenas criar um novo plugin para estender o comportamento atual.
  4. Delegação: Inversão de Controle é o fenômeno que observamos quando delegamos comportamento para ser implementado por outra pessoa, mas fornecemos os hooks / plug-ins / callbacks para isso acontecer. Projetamos o componente atual para inverter o controle para outro. Muitos frameworks da web são construídos com base neste princípio.

Inversão de Controle e Inversão de Controle com Containers

Os aplicativos ficam muito maiores do que apenas dois componentes.

  1. Crie um container (que manterá todas as dependências do seu aplicativo
  2. Torne essa dependência conhecida pelo container (especifique que é injetável)
  3. Resolva as dependências de que você precisa, pedindo ao container para injetá-las

Créditos

--

--

☕🇳🇿 - https://eduardorabelo.me

Love podcasts or audiobooks? Learn on the go with our new app.

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store