Introdução
A arquitetura de software robusta e sustentável é um objetivo crucial para qualquer projeto de desenvolvimento. No ecossistema de Node.js, isso não é diferente. A aplicação dos princípios de Inversão de Controle (IoC) e Injeção de Dependência (DI) pode elevar significativamente a qualidade do código em projetos TypeScript. Ao utilizar esses princípios em conjunto com o Express, criamos uma fundação sólida para aplicações escaláveis e fáceis de manter.
O que é Inversão de Controle?
Inversão de Controle é um princípio de design de software que desacopla os componentes de uma aplicação, transferindo o controle de fluxos e dependências para um 'container' ou framework. Em vez de um componente criar ou buscar as dependências necessárias, ele as recebe de uma fonte externa, geralmente um framework ou biblioteca especializada.
E Injeção de Dependência?
Injeção de Dependência é uma técnica de IoC onde um objeto recebe outras instâncias de objetos (dependências) de que necessita. Em vez de criar suas dependências internamente ou buscar globalmente, o objeto tem suas dependências 'injetadas' no momento da criação, geralmente por meio de construtores, métodos ou propriedades.
Implementação com TypeScript
Vamos explorar como implementar IoC e DI em uma API Express estruturada com rotas, controladores, serviços e repositórios.
Setup Inicial
Instale o pacote inversify
, uma biblioteca IoC para TypeScript, e habilite os decoradores no seu tsconfig.json
:
npm install inversify reflect-metadata
{
"compilerOptions": {
"experimentalDecorators": true,
"emitDecoratorMetadata": true
}
}
Criando o Container de Inversão de Controle
Usamos um container IoC para gerenciar nossas dependências. Aqui, definimos as 'bindings' que mapeiam as interfaces às suas implementações concretas.
inversify.config.ts
import { Container } from 'inversify';
import "reflect-metadata";
import { UserService } from './services/UserService';
import { UserRepository } from './repositories/UserRepository';
const container = new Container();
container.bind<UserService>('UserService').to(UserService);
container.bind<UserRepository>('UserRepository').to(UserRepository);
export { container };
É necessário adicionar o bind
no container
de injeção de dependência para todas as classes que você deseja que sejam injetáveis. Além disso, o uso do decorador @injectable
é essencial em cada classe injetável. Isso garante que você utilize sempre a mesma instância da classe em toda a aplicação, promovendo a reutilização e evitando a criação de múltiplas instâncias, o que é um princípio chave na injeção de dependência e na inversão de controle.
Aplicando a Injeção de Dependência
services/UserService.ts
import { inject, injectable } from 'inversify';
import { UserRepository } from '../repositories/UserRepository';
@injectable()
class UserService {
private userRepository: UserRepository;
constructor(@inject('UserRepository') userRepository: UserRepository) {
this.userRepository = userRepository;
}
// Métodos de negócio que utilizam `userRepository`
}
export { UserService };
repositories/UserRepository.ts
import { injectable } from 'inversify';
@injectable()
class UserRepository {
// Métodos para acessar o banco de dados
}
export { UserRepository };
Integrando com Express
controllers/UserController.ts
import { Request, Response } from 'express';
import { inject } from 'inversify';
import { UserService } from '../services/UserService';
class UserController {
private userService: UserService;
constructor(@inject('UserService') userService: UserService) {
this.userService = userService;
}
public async getUser(req: Request, res: Response) {
const user = await this.userService.getUserById(req.params.id);
res.json(user);
}
}
export { UserController };
app.ts
import express from 'express';
import { container } from './inversify.config';
import { UserController } from './controllers/UserController';
const app = express();
const userController = container.resolve(UserController);
app.get('/users/:id', (req, res) => userController.getUser(req, res));
const PORT = 3000;
app.listen(PORT, () => console.log(`Server running on port ${PORT}`));
Conclusão
A utilização de IoC e DI proporciona vários benefícios em projetos Node.js com TypeScript:
- Desacoplamento: As dependências entre as classes são minimizadas, facilitando a manutenção e o teste.
- Flexibilidade: A substituição de implementações de dependências torna-se trivial, o que é ideal para testes e para a evolução do projeto.
- Escalabilidade: A arquitetura da aplicação torna-se mais clara e a expansão do projeto é facilitada pela organização e modularidade.
Em suma, IoC e DI são princípios poderosos que, quando implementados corretamente, podem transformar a forma como construímos e gerenciamos aplicativos Express com TypeScript, conduzindo a um código mais limpo, testável e adaptável.
Top comments (2)
Muito bom, obrigado pela explicação Vitor!
Gostei bastante mano! Isso ajuda bastante na hora de criar dependências nos testes que sejam usadas apenas nos testes