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.
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:
├── 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.
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.
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.
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.
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.
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:
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.