Problema
“Clean Arch” virou pastas entities/usecases/adapters vazias ou frameworks dentro do domínio. O time para de entregar valor e a arquitetura vira desculpa para burocracia.
Solução
Aplicar o espírito: regras de negócio independentes de framework; dependências apontando para dentro; IO na borda. Começar pelo núcleo que muda menos (regras de preço, elegibilidade, políticas) e introduzir portas só onde há troca real (Postgres hoje, outro DB amanhã).
Arquitetura
domain/
order.ts # entidade + invariantes
application/
place-order.ts # caso de uso (orquestra)
ports/
order-repository.ts
payment-gateway.ts
infra/
prisma-order-repository.ts
stripe-gateway.ts
O caso de uso não importa Express ou Prisma — recebe portas injetadas.
Código
// application/place-order.ts
export class PlaceOrder {
constructor(
private readonly orders: OrderRepository,
private readonly payments: PaymentGateway
) {}
async execute(cmd: PlaceOrderCommand) {
const order = Order.create(cmd.items);
const charge = await this.payments.charge(order.totalCents, cmd.customerId);
await this.orders.save(order.paid(charge.id));
return order.id;
}
}// infra/http/order-controller.ts (adapter)
@Post()
async place(@Body() body: PlaceOrderBodyDto) {
return this.placeOrder.execute({ ...body });
}Performance
Camadas extras não precisam custar: evite alocar DTOs gigantes; use lazy init em adapters pesados; profile antes de “otimizar” domínio.
Melhorias futuras
Eventos de domínio + handlers; testes de contrato nas portas; anti-corruption layer para integrações legadas.
Conclusão
Clean Architecture é ferramenta de isolamento de risco, não religião de pastas. Demonstrar esse equilíbrio é forte sinal de senioridade.