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
- Introdução à Clean Architecture
- Princípios Fundamentais
- Configurando o Projeto
- Estrutura de Pastas
- Entidades
- Casos de Uso
- Interfaces
- Frameworks e Drivers
- Juntando Tudo
- Conclusão
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
- Independência: A lógica de negócios deve ser independente de UI, banco de dados ou sistemas externos.
- Testabilidade: O sistema deve ser fácil de testar.
- Separação de Responsabilidades: Diferentes partes do sistema devem ter responsabilidades distintas.
- 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.
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.
{
"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.
// 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.
// 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.
// 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.
// 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.
// 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:
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.
Top comments (2)
Gostei, tenho seguido a Clean Architecture, no entanto tenho uma dúvida se no type script uso DTO ou interface. O que vcs usam, classicamente a Interface seria mais pra comportamentos e não para dados, no typescript a comunidade parece usar para os dois. Qual seria melhor?
export type UserDTO = {
id: number;
name: string;
email: string;
password:string;
}
vs.
export interface UserInterface {
id:number;
name:string;
email:string;
password:string;
}
Muito boa a explicação!!