Inversão de Controle (IoC) e Injeção de Dependência em APIs Express com TypeScript
Tags: node, javascript, programming, typescript
Published: 2024-01-03 15:20:34

## 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`:

```bash
npm install inversify reflect-metadata
```

```json
{
  "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`

```typescript
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`

```typescript
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`

```typescript
import { injectable } from 'inversify';

@injectable()
class UserRepository {
  // Métodos para acessar o banco de dados
}

export { UserRepository };
```

### Integrando com Express

#### `controllers/UserController.ts`

```typescript
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`

```typescript
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.