Descrição da Estrutura de Pastas com princípios SOLID
Descrição da Estrutura de Pastas com princípios SOLID
/src
-
Diretório raiz contendo todo o código-fonte da aplicação.
/server.ts
- Arquivo na raiz responsável apenas por iniciar o servidor. Ele importa o módulo
app
de./app
para configurar o servidor e também acessa as variáveis de ambiente a partir do módulo./env
.
/app.ts
- Arquivo responsável por configurar o servidor Fastify, registrar as rotas e middlewares, e também por configurar o tratamento de erros.
/@types
- Armazena declarações de tipos personalizados ou extensões de tipos existentes.
/docs
- Documentação do projeto, incluindo especificações da API e outras informações relevantes.
/env
- Configurações específicas de ambiente.
/http
-
Código relacionado à camada HTTP da aplicação.
/controllers
- Controladores que lidam com a lógica de apresentação e delegam a lógica de negócios para os use-cases.
/[entidade]/routes.ts
- Arquivo responsável por definir as rotas da entidade específica. Importa e utiliza os middlewares e controllers associados a essas rotas.
/[entidade]/[ação].ts
- Arquivos que implementam a lógica específica para cada ação da entidade. Utiliza uma factory para executar o repositório correspondente.
/[entidade]/[ação].spec.ts
- Arquivos de teste E2E (End-to-End) para os endpoints. Cada teste cria e destrói um banco de dados para garantir o isolamento.
/middlewares
- Middlewares que fazem o pré-processamento de requisições e o pós-processamento de respostas.
- Controladores que lidam com a lógica de apresentação e delegam a lógica de negócios para os use-cases.
/lib
- Bibliotecas e utilitários que não pertencem a uma camada específica da aplicação, como por exemplo, bibliotecas para manipulação de datas ou strings.
/repositories
- Abstrações para acesso ao banco de dados.
/in-memory
- Repositórios que usam armazenamento em memória (útil para testes ou desenvolvimento).
[in-memory-entidadeName.ts]
(ex: in-memory-users-repository.ts)
/supabase
- Repositórios que interagem com o Supabase como banco de dados.
/[supabase-entidadeName-repositorie.ts]
- Arquivo dedicado para realizar consultas ao banco de dados Supabase.
- Repositórios que usam armazenamento em memória (útil para testes ou desenvolvimento).
/use-cases
- Casos de uso da aplicação, ou seja, lógica de negócios.
/errors
[error].ts
(ex:invalid-credentials-error.ts
)- Arquivos dedicados a representar erros específicos que podem ser lançados pelos casos de uso e capturados pelos middlewares.
/factories
make-[entidade]-use-case.ts
(ex:make-register-use-case.ts
)- Fábricas responsáveis por instanciar os casos de uso e suas dependências, como repositórios.
[nome_da_entidade].ts
(ex:register.ts
)- Arquivos de casos de uso que implementam as regras de negócio e recebem o repositório como uma dependência.
[nome_da_entidade].spec.ts
(ex:register.spec.ts
)- Arquivos de teste unitário para os casos de uso, usando um repositório "in-memory" para agilizar os testes.
/utils
- Utilitários e funções auxiliares que podem ser usadas em diferentes partes da aplicação.
/validators
- Validadores de entrada para as rotas e outras formas de dados.
- Arquivo na raiz responsável apenas por iniciar o servidor. Ele importa o módulo
Passo a Passo com SOLID - Arquivo por Arquivo
1. server.ts
- Single Responsibility Principle (SRP): Este arquivo tem a única responsabilidade de iniciar o servidor.
2. app.ts
-
Single Responsibility Principle (SRP): Este arquivo é responsável por configurar o servidor Fastify e seus recursos, sem envolver lógica de negócios ou acesso a dados.
-
Open/Closed Principle (OCP): O arquivo está aberto para extensão, você pode adicionar mais rotas, middlewares ou configurações de plugins sem modificar o código existente.
-
Dependency Inversion Principle (DIP): As dependências, como rotas e plugins, são injetadas através do método
register
, tornando o código mais modular e fácil de testar.
3. routes.ts
-
Single Responsibility Principle (SRP): O arquivo tem a única responsabilidade de definir as rotas associadas a uma entidade específica.
-
Open/Closed Principle (OCP): O arquivo está aberto para extensão, já que novas rotas ou middlewares podem ser adicionadas facilmente.
-
Dependency Inversion Principle (DIP): A função
usersRoutes
recebe um objetoFastifyInstance
como argumento, tornando o código mais modular e fácil de testar.
4. [ação].ts
(por exemplo, register.ts
)
-
Single Responsibility Principle (SRP): Cada arquivo
[ação].ts
tem a responsabilidade única de implementar a lógica para uma ação específica da entidade. -
Open/Closed Principle (OCP): Extensível para novas regras ou lógicas de negócios sem a necessidade de modificar o código existente.
-
Dependency Inversion Principle (DIP): Utiliza uma factory para executar a ação correspondente no repositório, tornando o código mais modular e testável.
-
Interface Segregation Principle (ISP): Cada ação tem seu próprio arquivo, tornando as interfaces mais específicas e orientadas para o cliente.
5. [supabase-entidadeName-repositorie.ts]
-
Single Responsibility Principle (SRP): Este arquivo tem a única responsabilidade de executar as consultas ao banco de dados Supabase.
-
Open/Closed Principle (OCP): Extensível para novas consultas ou lógicas de acesso aos dados sem modificar o código existente.
-
Dependency Inversion Principle (DIP): As consultas são abstraídas em métodos específicos, tornando o código mais modular e testável.
6. nome_da_entidade.ts
(ex: register.ts
)
-
Single Responsibility Principle (SRP): Implementa as regras de negócio para uma funcionalidade específica.
-
Open/Closed Principle (OCP): Extensível para novas regras de negócio sem a necessidade de modificar o código existente.
-
Dependency Inversion Principle (DIP): As regras de negócio são desacopladas do repositório. Se o repositório mudar no futuro, as regras de negócio não precisam ser alteradas.
-
Liskov Substitution Principle (LSP): O caso de uso espera uma abstração (um repositório) e é imutável em relação aos detalhes, permitindo a substituição do repositório sem afetar o caso de uso.
7. [nome_da_entidade].spec.ts
(ex: register.spec.ts
)
-
Single Responsibility Principle (SRP): Testa uma única funcionalidade ou caso de uso.
-
Open/Closed Principle (OCP): Novos testes podem ser adicionados sem alterar os testes existentes.
-
Liskov Substitution Principle (LSP): Utiliza um repositório "in-memory", permitindo que o repositório real seja substituído nos testes sem afetar a lógica do caso de uso.
-
Dependency Inversion Principle (DIP): O caso de uso é testado isoladamente, com o repositório sendo uma dependência injetada.
8. [ação].spec.ts
(ex: register.spec.ts
)
-
Single Responsibility Principle (SRP): Cada teste E2E foca em testar uma única rota ou fluxo de interação do usuário.
-
Open/Closed Principle (OCP): Novos testes E2E podem ser adicionados sem alterar os testes existentes.
-
Liskov Substitution Principle (LSP): Utiliza um banco de dados temporário, permitindo que o ambiente de teste seja totalmente controlado e isolado.
-
Dependency Inversion Principle (DIP): Os testes E2E são independentes dos detalhes de implementação, focando apenas no comportamento esperado da aplicação.
9. make-[entidade]-use-case.ts
(ex: make-register-use-case.ts
)
-
Single Responsibility Principle (SRP): Responsável por instanciar um caso de uso e suas dependências, fazendo uma única coisa.
-
Open/Closed Principle (OCP): A fábrica é extensível; se precisarmos adicionar novas dependências ou modificar as existentes, podemos fazê-lo sem alterar o caso de uso.
-
Liskov Substitution Principle (LSP): A fábrica pode instanciar qualquer repositório que siga a interface definida, permitindo fácil substituição.
-
Interface Segregation Principle (ISP): O caso de uso depende apenas das interfaces (neste caso, repositórios) de que realmente precisa.
-
Dependency Inversion Principle (DIP): O caso de uso não depende de uma implementação concreta do repositório, mas de uma abstração, facilitando a substituição e testes.
10. errors/[error].ts
(ex: invalid-credentials-error.ts
)
-
Single Responsibility Principle (SRP): Cada arquivo de erro é responsável por definir um tipo específico de erro, fazendo uma única coisa.
-
Open/Closed Principle (OCP): Novos tipos de erros podem ser adicionados sem alterar o código existente.
-
Liskov Substitution Principle (LSP): Eles podem ser substituídos ou estendidos para fornecer detalhes adicionais se necessário.
-
Interface Segregation Principle (ISP): Não aplicável neste contexto.
-
Dependency Inversion Principle (DIP): Os arquivos de erro podem ser importados como abstrações onde for necessário, tornando o código mais flexível.
Já enfrentei inúmeras dificuldades com a manutenção de APIs no passado e, sinceramente, não gostaria de repetir esses erros. Então decidi fazer um guia mais ou menos correlacionando cada arquivo principal com SOLID.
Como diz o ditado "mata a cobra e mostra o pau" 😂😂😂 segue um repositório com a implementação API-Rest-Node-SOLID.
Vamos la galera de sugestões para deixar isso melhor.
Grande abraço.
O app.ts parece ter várias responsabilidades distintas, o que pode ser um ponto de discussão em relação ao SRP. A configuração do servidor, registro de rotas e tratamento de erros são, de fato, responsabilidades diferentes. Isso me lembra um restaurante que serve pizza, sushi e hambúrgueres e diz que trabalha só com comida.
Estou destacando isto, por que achei curioso e interessante a aplicação dos princípios SOLID na estruturação do projeto, mas confesso que enxergo muito problemas. Parece que esta tentando encaixar princípios complexos em uma estrutura simplista - e sem necessidade.
É importante notar que os princípios foram desenvolvidos com base nas observações e práticas da programação orientada a objetos, e seu criador Uncle Bob, é uma figura proeminente nesse campo, principalmente na comunidade Java.
O Node.js, por outro lado, é uma plataforma assíncrona e orientada a eventos, o que a torna muito distinta da programação orientada a objetos tradicional.
O SOLID é uma ferramenta útil sim, em qualquer lingaugem, mas não é uma fórmula mágica para resolver todos os problemas. Lembre-se, no final do dia, o importante é criar código de qualidade, independentemente de quantos princípios SOLID estiver seguindo.