Container de Injeção de Dependência para JavaScript (Node.js) — Awilix
No desenvolvimento moderno com Node.js, a pureza da arquitetura e a testabilidade do código estão em primeiro plano. Uma das ferramentas-chave para alcançar esses objetivos é a injeção de dependências (Dependency Injection, DI). A biblioteca Awilix fornece um container DI poderoso, performático e flexível para JavaScript e TypeScript, ideal para aplicações server-side em Node.js. Neste artigo, vamos detalhar como usar Awilix para gerenciar dependências, melhorar a modularidade e simplificar os testes.
O que é Injeção de Dependência e para que serve um container?
Injeção de Dependência é um padrão de projeto no qual um objeto recebe suas dependências externamente, em vez de criá-las ele mesmo. Isso implementa o princípio Inversion of Control (IoC). Um container é uma ferramenta que automatiza o processo de criação e fornecimento dessas dependências. Em vez de criar manualmente instâncias de classes e passá-las umas para as outras, você descreve as regras de montagem no container, e ele resolve todas as cadeias de dependências.
Principais vantagens de usar um container DI:
- Redução do acoplamento do código: Módulos dependem de abstrações, não de implementações concretas.
- Simplificação dos testes: Fácil substituir serviços reais por mocks durante testes unitários.
- Gerenciamento centralizado: Toda a configuração de dependências fica em um só lugar, facilitando a refatoração.
- Gerenciamento do ciclo de vida: O container pode controlar por quanto tempo um objeto vive — uma única instância para toda a aplicação (singleton) ou uma nova a cada requisição.
Instalação do Awilix
Para começar, instale a biblioteca via npm:
npm install awilixAwilix não possui dependências externas e funciona tanto com módulos CommonJS quanto ESM.
Conceitos principais do Awilix
Awilix é construído em torno de alguns conceitos-chave:
- Container (contêiner): O objeto principal que armazena e fornece as dependências.
- Registration (registro): O processo de associar um nome (token) a uma fábrica, classe ou valor.
- Resolver (resolvedor): Objeto que define como criar ou obter a dependência. Por exemplo,
asClass(),asFunction(),asValue(). - Lifetime (tempo de vida): Indica quantas vezes o objeto será criado:
ScopedLifeTime(uma vez por escopo),SingletonLifeTime(uma vez durante toda a execução) ouTransientLifeTime(sempre uma nova instância).
Exemplo de uso: serviço para trabalhar com usuários
Vamos considerar um exemplo simples. Suponha que temos um banco de dados, um repositório de usuários e um serviço para a lógica de negócios.
1. Definindo as classes
// database.jsclass Database { constructor() { this.connection = 'Connected to DB'; } query(sql) { return `Result for: ${sql}`; }}
// userRepository.jsclass UserRepository { constructor({ database }) { this.db = database; } findById(id) { return this.db.query(`SELECT * FROM users WHERE id = ${id}`); }}
// userService.jsclass UserService { constructor({ userRepository }) { this.repo = userRepository; } getUser(id) { return this.repo.findById(id); }}Observe os construtores: eles recebem um objeto com as dependências. Esta é uma prática padrão para Awilix.
2. Configurando o container
const { createContainer, asClass, asValue, Lifetime } = require('awilix');
const container = createContainer();
// Registramos a Também na biblioteca
Trabalhando com CSV em Rust: bibliotecas csv, serde e processamento eficiente de dados
Qt para C++: guia completo com exemplos
Envio de SMS em Go (Golang): integração com API de biblioteca
Internacionalização (i18n) em Java: guia completo com exemplos