Implementando Clean Architecture com TypeScript
Tags: typescript, cleancoding, javascript, coding
Published: 2024-07-04 19:33:18

Clean Architecture é uma filosofia de design de software que visa criar sistemas fáceis de manter, testar e entender. Ela enfatiza a separação de responsabilidades, garantindo que cada parte do sistema tenha uma única responsabilidade. Neste artigo, exploraremos como implementar Clean Architecture usando TypeScript.

## Índice

1. [Introdução à Clean Architecture](#introducao-a-clean-architecture)
2. [Princípios Fundamentais](#principios-fundamentais)
3. [Configurando o Projeto](#configurando-o-projeto)
4. [Estrutura de Pastas](#estrutura-de-pastas)
5. [Entidades](#entidades)
6. [Casos de Uso](#casos-de-uso)
7. [Interfaces](#interfaces)
8. [Frameworks e Drivers](#frameworks-e-drivers)
9. [Juntando Tudo](#juntando-tudo)
10. [Conclusão](#conclusao)

## Introdução à Clean Architecture

Clean Architecture, introduzida por Robert C. Martin (Uncle Bob), proporciona uma separação clara entre as diferentes partes de um sistema de software. A ideia principal é manter a lógica de negócios central independente de fatores externos, como bancos de dados, UI ou frameworks.

## Princípios Fundamentais

1. **Independência**: A lógica de negócios deve ser independente de UI, banco de dados ou sistemas externos.
2. **Testabilidade**: O sistema deve ser fácil de testar.
3. **Separação de Responsabilidades**: Diferentes partes do sistema devem ter responsabilidades distintas.
4. **Manutenibilidade**: O sistema deve ser fácil de manter e evoluir.

## Configurando o Projeto

Primeiro, vamos configurar um projeto TypeScript. Você pode usar `npm` ou `yarn` para inicializar um novo projeto.

```bash
mkdir clean-architecture-ts
cd clean-architecture-ts
npm init -y
npm install typescript ts-node @types/node --save-dev
```

Crie um arquivo `tsconfig.json` para configurar o TypeScript.

```json
{
  "compilerOptions": {
    "target": "ES6",
    "module": "commonjs",
    "outDir": "./dist",
    "rootDir": "./src",
    "strict": true,
    "esModuleInterop": true
  }
}
```

## Estrutura de Pastas

Um projeto com clean architecture geralmente tem a seguinte estrutura de pastas:

```
src/
├── entities/
├── usecases/
├── interfaces/
├── frameworks/
└── main.ts
```

### Entidades

Entidades representam a lógica de negócios central. Elas são a parte mais importante do sistema e devem ser independentes de fatores externos.

```typescript
// src/entities/user.entity.ts
export class User {
	constructor(id: string, public email: string, public password:string) {}

	static create(email: string, password: string) {
		const userId = uuid()
		return new User(userId, email, password)
	}
}
```

### Casos de Uso

Casos de uso contêm as regras de negócios específicas da aplicação. Eles orquestram a interação entre entidades e interfaces.

```typescript
// src/usecases/create-user.usecase.ts
import { User } from "../entities/user.entity";
import { UsersRepository } from "../interfaces/users.repository"

interface CreateUserRequest {
  email: string;
  password: string;
}

export class CreateUserUseCase {
  constructor(private userRepository: UserRepository) {}

  async execute(request: CreateUserRequest): Promise<void> {
    const user = User.create(request.email, request.password)
    await this.userRepository.save(user);
  }
}
```

### Interfaces

Interfaces são os contratos entre os casos de uso e o mundo externo. Elas podem incluir repositórios, serviços ou qualquer sistema externo.

```typescript
// src/interfaces/users.repository.ts
import { User } from "../entities/user.entity";

export interface UserRepository {
  save(user: User): Promise<void>;
}
```

### Frameworks e Drivers

Frameworks e drivers contêm os detalhes de implementação das interfaces. Eles interagem com sistemas externos, como bancos de dados ou APIs.

```typescript
// src/frameworks/in-memory-users.repository.ts
import { User } from "../entities/User";
import { UserRepository } from "../interfaces/users.repository";

export class InMemoryUsersRepository implements UserRepository {
  private users: User[] = [];

  async save(user: User): Promise<void> {
    this.users.push(user);
  }
}
```

## Juntando Tudo

Finalmente, vamos criar um ponto de entrada para conectar tudo.

```typescript
// src/main.ts
import { CreateUser } from "./usecases/create-user.usecase";
import { InMemoryUserRepository } from "./frameworks/in-memory-users.repository";

const userRepository = new InMemoryUserRepository();
const createUser = new CreateUserUseCase(userRepository);

createUser.execute({ email: "john.doe@example.com", password: "123456" })
  .then(() => console.log("User created successfully"))
  .catch(err => console.error("Failed to create user", err));
```

Compile e execute o projeto:

```bash
tsc
node dist/main.js
```

## Conclusão

Seguindo os princípios da Clean Architecture, podemos criar um sistema que é manutenível, testável e adaptável a mudanças. TypeScript fornece tipagem forte e recursos modernos de JavaScript que ajudam a impor esses princípios. Com uma clara separação de responsabilidades, nosso código se torna mais fácil de entender e evoluir ao longo do tempo.