Não sou um grande fã de grandes frameworks como NestJS; Sempre gostei da liberdade de construir meu software do jeito que eu quero com a estrutura que eu decido de forma leve. Mas algo que gostei ao testar o NestJS foi a injeção de dependência.
Injeção de Dependência (DI) é um padrão de design que nos permite desenvolver código fracamente acoplado, eliminando a responsabilidade de criar e gerenciar dependências de nossas classes. Esse padrão é crucial para escrever aplicativos passíveis de manutenção, testáveis e escaláveis. No ecossistema TypeScript, o TSyringe se destaca como um contêiner de injeção de dependência poderoso e leve que simplifica esse processo.
TSyringe é um contêiner leve de injeção de dependência para aplicativos TypeScript/JavaScript. Mantido pela Microsoft em seu GitHub (https://github.com/microsoft/tsyringe), ele usa decoradores para fazer injeção de Construtor. Em seguida, ele usa um contêiner de Inversão de Controle para armazenar as dependências com base em um token que você pode trocar por uma instância ou um valor.
Antes de mergulhar no TSyringe, vamos explorar brevemente o que é injeção de dependência e por que ela é importante.
Injeção de dependência é uma técnica em que um objeto recebe suas dependências de fontes externas em vez de criá-las ele mesmo. Essa abordagem oferece vários benefícios:
Primeiro, vamos configurar o TSyringe em seu projeto TypeScript:
npm install tsyringe reflect-metadata
Em seu tsconfig.json, certifique-se de ter as seguintes opções:
{ "compilerOptions": { "experimentalDecorators": true, "emitDecoratorMetadata": true } }
Importe metadados de reflexão no ponto de entrada do seu aplicativo:
import "reflect-metadata";
O ponto de entrada do seu aplicativo é, por exemplo, o layout raiz em Next.js 13 , ou pode ser o arquivo principal em um pequeno aplicativo Express.
Vamos pegar o exemplo da introdução e adicionar o açúcar da seringa TS:
Vamos começar com o adaptador.
// @/adapters/userAdapter.ts import { injectable } from "tsyringe" @injectable() class UserAdapter { constructor(...) {...} async fetchByUUID(uuid) {...} }
Notou o decorador @injectable()? É para dizer ao TSyringe que esta classe pode ser injetada em tempo de execução.
Então meu serviço está usando o adaptador que acabamos de criar. Vamos injetar esse adaptador em meu serviço.
// @/core/user/user.service.ts import { injectable, inject } from "tsyringe" ... @injectable() class UserService { constructor(@inject('UserAdapter') private readonly userAdapter: UserAdapter) {} async fetchByUUID(uuid: string) { ... const { data, error } = await this.userAdapter.fetchByUUID(uuid); ... } }
Aqui também usei o decorador @injectable porque o Service será injetado na minha classe de comando, mas também adicionei o decorador @inject nos parâmetros do construtor. Este decorador diz ao TSyringe para fornecer a instância ou o valor que ele possui para o token UserAdapter para a propriedade userAdapter em tempo de execução.
E por último, mas não menos importante, a raiz do meu Core: a classe de comando (muitas vezes chamada erroneamente de usecase).
// @/core/user/user.commands.ts import { inject } from "tsyringe" ... @injectable() class UserCommands { constructor(@inject('UserService') private readonly userService: UserService) {} async fetchByUUID(uuid) { ... const { data, error } = this.userService.fetchByUUID(uuid); ... } }
Neste ponto, dissemos ao TSyringe o que será injetado e o que injetar no construtor. Mas ainda não fizemos nossos containers para armazenar as dependências. Podemos fazer isso de duas maneiras:
Podemos criar um arquivo com nosso registro de injeção de dependência:
// @/core/user/user.dependencies.ts import { container } from "tsyringe" ... container.register("UserService", {useClass: UserService}) // associate the UserService with the token "UserService" container.register("UserAdapter", {useClass: UserAdapter}) // associate the UserAdapter with the token "UserAdapter" export { container }
Mas também podemos usar o decorador @registry.
// @/core/user/user.commands.ts import { inject, registry, injectable } from "tsyringe" ... @injectable() @registry([ { token: 'UserService', useClass: UserService }, { token: 'UserAdapter', useClass: UserAdapter }, ]) export class UserCommands { constructor(@inject('UserService') private readonly userService: UserService) {} async fetchByUUID(uuid) { ... const { data, error } = this.userService.fetchByUUID(uuid); ... } } container.register("UserCommands", { useClass: UserCommands}) export { container }
Ambos os métodos têm prós e contras, mas no final das contas, é uma questão de gosto.
Agora que nosso contêiner está preenchido com nossas dependências, podemos obtê-las do contêiner conforme necessário usando o método resolve do contêiner.
import { container, UserCommands } from "@/core/user/user.commands" ... const userCommands = container.resolve("UserCommands") await userCommands.fetchByUUID(uuid) ...
Este exemplo é bastante simples, pois cada classe depende apenas de outra, mas nossos serviços podem depender de muitos, e a injeção de dependência realmente ajudaria a manter tudo organizado.
Mas espere! Não me deixe assim! E os testes?
Nossas injeções também podem nos ajudar a testar nosso código, enviando objetos simulados diretamente para nossas dependências. Vejamos um exemplo de código:
import { container, UserCommands } from "@/core/user/user.commands" describe("test ftw", () => { let userAdapterMock: UserAdapterMock let userCommands: UserCommands beforeEach(() => { userAdapterMock = new UserAdapter() container.registerInstance("UserAdapter", userAdapter) userCommands = container.resolve ("UserCommands") }); ... });
Agora o token UserAdapter contém uma simulação que será injetada nas classes dependentes.
Usar tokens para nomenclatura: em vez de usar strings literais para tokens de injeção, crie tokens constantes:
export const USER_REPOSITORY_TOKEN = Symbol("UserRepository");
Contêineres com escopo: use contêineres com escopo para dependências com escopo de solicitação em aplicativos da web.
Não abuse do DI: Nem tudo precisa ser injetado. Use DI para preocupações transversais e dependências configuráveis.
Se você chegou até aqui, quero agradecer pela leitura. Espero que você tenha achado este artigo instrutivo. Lembre-se de sempre considerar as necessidades específicas do seu projeto ao implementar injeção de dependência e padrões arquitetônicos.
Curtidas e comentários são as melhores maneiras de melhorar.
Boa codificação!
Isenção de responsabilidade: Todos os recursos fornecidos são parcialmente provenientes da Internet. Se houver qualquer violação de seus direitos autorais ou outros direitos e interesses, explique os motivos detalhados e forneça prova de direitos autorais ou direitos e interesses e envie-a para o e-mail: [email protected]. Nós cuidaremos disso para você o mais rápido possível.
Copyright© 2022 湘ICP备2022001581号-3