Este projeto representa uma implementação prática dos conceitos de Clean Architecture em TypeScript, demonstrando como aplicar os princípios fundamentais dessa arquitetura em uma aplicação Node.js real.
A Clean Architecture é um termo criado por Robert C. Martin (Uncle Bob) em 2012, que posteriormente se tornou um livro. Embora seja frequentemente tratada como uma buzzword, sua essência está na proteção do domínio da aplicação, no baixo acoplamento entre as camadas e na orientação a casos de uso.
Uma observação interessante é que Robert C. Martin fala especificamente sobre Clean Architecture em apenas 7 páginas do livro. Tudo que ele aborda especificamente sobre Clean Architecture está literalmente disponível em um artigo em seu blog.
O estudo da Clean Architecture é fundamental para reforçar conhecimento e remover gaps básicos que muitas vezes nem percebemos ter. Os principais benefícios incluem:
- Melhor compreensão sobre componentes
- Entendimento aprofundado sobre arquitetura
- Conhecimento sobre limites arquiteturais
- Percepção aprimorada sobre regras de negócios
A arquitetura define o formato que o software terá, incluindo a divisão de componentes e a comunicação entre eles. Uma boa arquitetura facilita o processo de desenvolvimento, deploy, operação e manutenção.
O objetivo principal da arquitetura é dar suporte ao ciclo de vida do sistema. Uma boa arquitetura torna o sistema fácil de entender, fácil de desenvolver, fácil de manter e fácil de implantar. O objetivo final é minimizar o custo de vida útil do sistema e maximizar a produtividade do programador.
As regras de negócio trazem o real valor para o software, enquanto os detalhes ajudam a suportar essas regras. Os detalhes não devem impactar nas regras de negócio. Frameworks, banco de dados e APIs não devem impactar as regras principais. Como enfatizado no DDD (Domain-Driven Design), é preciso atacar a complexidade no coração do software.
Os Use Cases representam a intenção e a clareza de cada comportamento do software. Eles são fundamentais para manter os detalhes separados das regras de negócio, garantindo que frameworks, banco de dados e APIs não impactem as regras principais.
Existe uma tendência de reaproveitar Use Cases por serem muito parecidos. Por exemplo, "Alterar" versus "Inserir": ambos consultam se o registro existe e persistem dados, mas são Use Cases diferentes. Isso ocorre porque, de acordo com o SRP (Single Responsibility Principle), eles mudam por razões diferentes. É importante distinguir entre duplicação real e acidental.
Tudo que não impacta diretamente nas regras de negócio deve estar em um limite arquitetural diferente. Por exemplo, não será o frontend ou o banco de dados que mudarão as regras de negócio da aplicação.
No final do dia, tudo se resume a um input que retorna um output. Por exemplo: criar um pedido (dados do pedido como input) resulta em um pedido criado (dados de retorno do pedido). Simplifique seu raciocínio ao criar um software sempre pensando em Input e Output.
Os DTOs são utilizados para trafegar dados entre os limites arquiteturais. São objetos anêmicos, sem comportamento, que contêm dados (Input ou Output). Eles não possuem regras de negócio, não possuem comportamento e não fazem nada além de transportar dados.
O fluxo típico é: API → Controller → Use Case → Entity. O Controller cria um DTO com os dados recebidos e envia para o Use Case. O Use Case executa seu fluxo, pega o resultado, cria um DTO para output e retorna para o Controller.
Os Presenters são objetos de transformação que adequam o DTO de Input no formato correto para entregar o resultado. É importante lembrar que um sistema pode ter diversos formatos de entrega: XML, JSON, Protobuf, GraphQL, CLI, etc.
Exemplo de uso:
input = new CategoryInputDTO('name')
output = CreateCategoryUseCase(input)
jsonResult = CategoryPresenter(output).toJson()
xmlResult = CategoryPresenter(output).toXML()
As Entities da Clean Architecture são diferentes das Entities do DDD. A Clean Architecture define Entity como camada de regras de negócio que se aplicam em qualquer situação. Não há definição explícita de como criar as Entities, mas normalmente utilizamos táticas do DDD. Na prática, Entities equivalem a Agregados + Domain Services.
src/
├── domain/ # Camada de Domínio
│ ├── @shared/ # Componentes compartilhados
│ ├── customer/ # Agregado Customer
│ ├── product/ # Agregado Product
│ └── checkout/ # Agregado Checkout
├── infra/ # Camada de Infraestrutura
│ ├── api/ # API REST (Express)
│ ├── customer/ # Repositórios de Customer
│ ├── product/ # Repositórios de Product
│ └── order/ # Repositórios de Order
└── usecase/ # Casos de Uso
├── customer/ # Use Cases de Customer
└── product/ # Use Cases de Product
- Entities: Regras de negócio fundamentais
- Value Objects: Objetos imutáveis que representam conceitos do domínio
- Domain Services: Lógica de negócio que não pertence a uma entidade específica
- Repository Interfaces: Contratos para persistência de dados
- Domain Events: Eventos que representam mudanças no domínio
- Application Services: Orquestração de fluxos de negócio
- DTOs: Objetos de transferência de dados
- Input/Output Boundaries: Interfaces para entrada e saída de dados
- Database: Implementação de repositórios com Sequelize
- API: Controllers e rotas REST
- External Services: Integrações com serviços externos
- Node.js - Runtime JavaScript
- TypeScript - Superset tipado do JavaScript
- Express - Framework web
- Sequelize - ORM para banco de dados
- SQLite - Banco de dados para desenvolvimento
- Jest - Framework de testes
- Yup - Validação de esquemas
- UUID - Geração de identificadores únicos
- Node.js 18+
- Yarn ou npm
- Clone o repositório:
git clone <repository-url>
cd CleanArch- Instale as dependências:
yarn install
# ou
npm install- Execute os testes:
yarn test
# ou
npm test- Inicie o servidor de desenvolvimento:
yarn dev
# ou
npm run devO projeto possui cobertura completa de testes incluindo:
- Unit Tests: Testes unitários para entities e use cases
- Integration Tests: Testes de integração para use cases
- E2E Tests: Testes end-to-end para APIs
# Executar todos os testes
yarn test
# Executar testes específicos
yarn test -- --testNamePattern="Customer"- Representam as regras de negócio mais importantes
- Independentes de frameworks e tecnologias
- Aplicam-se em qualquer contexto
- Exemplo:
Customer,Product,Order
- Representam a intenção do sistema
- Orquestram o fluxo entre entities
- Independentes de detalhes de implementação
- Seguem o princípio SRP (Single Responsibility Principle)
- Controllers: Adaptam dados da web para use cases
- Presenters: Formatam dados para diferentes tipos de saída
- Repositories: Abstraem acesso a dados
- Banco de dados (SQLite/Sequelize)
- Framework web (Express)
- Ferramentas externas
CustomerFactory: Criação de customersProductFactory: Criação de productsOrderFactory: Criação de orders
EventDispatcher: Gerenciamento de eventos de domínioEventHandler: Manipuladores de eventos específicos
- Abstrações para acesso a dados
- Implementações específicas por tecnologia
- Inversão de dependência
- Transferência de dados entre camadas
- Objetos anêmicos sem comportamento
- Input e Output bem definidos
HTTP Request → Controller → Use Case → Entity → Repository → Database
↓ ↓ ↓
DTO → Domain → Events
↓ ↓ ↓
HTTP Response ← Presenter ← Use Case ← Event Handler
- Input: Cliente faz uma requisição HTTP
- Controller: Cria um DTO com os dados recebidos
- Use Case: Executa a lógica de negócio
- Entity: Aplica regras de domínio
- Repository: Persiste dados
- Output: Retorna resultado formatado pelo Presenter
- Trazem o real valor para o software
- Devem ser protegidas e isoladas
- Não devem ser impactadas por detalhes técnicos
- Frameworks, banco de dados, APIs
- Suportam as regras de negócio
- Podem ser alterados sem impactar o core
- Testabilidade: Fácil de testar isoladamente
- Manutenibilidade: Código organizado e limpo
- Flexibilidade: Fácil de adicionar novos recursos
- Independência: Desacoplado de frameworks
- Escalabilidade: Preparado para crescimento
Este projeto está sob a licença MIT. Veja o arquivo LICENSE para mais detalhes.