Clean Code com NestJs e Typescript

Clean Architecture

Então, o que é arquitetura limpa?

The Clean Architecture
  • Camadas: Cada anel representa uma camada isolada na aplicação.
  • Dependência: A direção da dependência é de fora para dentro. Isso significa que a camada de entidades é independente e a camada de frameworks (web, UI, etc.) depende de todas as outras camadas.
  • Entidades: Contém todas as entidades de negócios que constroem nossa aplicação.
  • Casos de uso: é aqui que centralizamos nossa lógica. Cada caso de uso orquestra toda a lógica para um caso de uso de negócios específico (por exemplo, adicionar novos clientes ao sistema).
  • Controllers e presenter: Nossos controllers, presenters e gateways são camadas intermediárias. Você pode pensar neles como um portão de entrada e saída para os casos de uso.
  • Frameworks: Esta camada possui todas as implementações específicas. O banco de dados, os frameworks da web, os frameworks de tratamento de erros, etc. Robert C. Martin descreve essa camada: “Essa camada é para onde vão todos os detalhes. A web é um detalhe. O banco de dados é um detalhe. Nós mantemos essas coisas do lado de fora, onde elas podem causar pouco dano.”

Exemplo de aplicativo

Nosso aplicativo de exemplo representará um microsserviço simples que oferece suporte a operações CRUD em um repositório de livros.

  • Injeção de dependência
  • Separação de código com módulos
  • Controllers
  • Middleware
  • Filtros
  • Guardas

Camada de Entidades e Casos de Uso

  • Camada de Entidades: Contém todas as entidades de negócios que constroem nossa aplicação.
  • Camada de casos de uso: contém todos os cenários de negócios que nosso aplicativo oferece suporte.
Camadas principais

Entidades

As entidades de negócios em nosso aplicativo são:

Author

  • Id
  • First Name
  • Last Name

Genre

  • Name

Book

  • Id
  • Title
  • Author
  • Genre
  • Publish Date

Casos de uso

É aqui que centralizamos nossa lógica. Cada caso de uso orquestra toda a lógica para um caso de uso de negócios específico. Nossa API de aplicativo precisa oferecer suporte a esses casos de uso (eu escolhi uma amostra deles):

  • Obter uma lista de todos os livros.
  • Obtenha detalhes de um único livro.
  • Adicionar novo livro.
  • Adicionar novo autor.

Adicionar caso de uso de livro

Vamos examinar e mergulhar no caso de uso “Adicionar novo livro”. As principais responsabilidades do caso de uso são:

  • Validação de regras de negócios.
  • Verifique se o livro não existe no DB.
  • Crie um novo objeto de livro.
  • Persista nosso novo livro em DB.
  • Atualize o sistema CRM da biblioteca.
  • Serviços de banco de dados: O caso de uso precisa persistir os detalhes do livro e verificar se ele não existe no sistema. Essa funcionalidade pode ser implementada como uma classe que chama SQL ou MongoDB, por exemplo.
  • Serviços de CRM: O caso de uso precisa notificar o aplicativo de CRM da biblioteca sobre o novo livro. Esta funcionalidade pode ser implementada como um serviço que chama um sistema externo (pode ser qualquer sistema).
Dependência de abstração

Dependências de casos de uso

Vamos definir nossas abstrações, precisamos:

  • Abstração de serviços de dados
  • Abstração de serviço de CRM
  • Repositório de livros
  • Repositório do autor
  • Repositório de gêneros

Principais abstrações

Você pode encontrar todas as abstrações em nosso aplicativo de exemplo em src/core/abstracts.

  • Repositório genérico : Cada repositório de entidade precisa dar suporte a operações básicas de crud. Eu criei um repositório genérico que servirá como classe abstrata para todos os repositórios de entidades, se precisarmos de funcionalidades diferentes para cada repositório você pode defini-los separadamente. Eu selecionei apenas a funcionalidade básica do repositório, em seu aplicativo real você pode definir a funcionalidade necessária para todos/cada repositórios.
  • Data Services: Exponha todos os repositórios de entidades.

Código do Caso de uso

  • Nosso caso de uso adicionar livro depende apenas das abstrações. No construtor injetamos apenas os IDataServices e ICrmServices.
  • Utilizamos os nossos serviços sem saber qual é a real implementação dos mesmos.
  • As alterações na implementação de serviços externos não afetariam nossa lógica de negócios de caso de uso.

Controllers e Presenter

Controllers

Controller

Na arquitetura limpa, o trabalho do controller é:

  • Receba a entrada do usuário — algum tipo de DTO.
  • Valide a higienização de entrada do usuário.
  • Converta a entrada do usuário em um modelo que o caso de uso espera. Por exemplo, faça formatos de data e conversão de string para inteiro.
  • Chame o caso de uso e passe o novo modelo.

Presenter

O presenter obterá dados do repositório do aplicativo e, em seguida, criará uma resposta formatada para o cliente. Suas principais responsabilidades incluem:

  • Formatar strings e datas.
  • Adicione dados de apresentação, como sinalizadores.
  • Prepare os dados a serem exibidos na interface do usuário.
  • A validação é feita por NestJs no objeto bookDto
  • Estamos usando o bookFactoryService para converter nosso DTO em um objeto de livro de negócios
  • Chama nosso serviço de casos de uso
  • Crie a resposta ao consumidor

Frameworks

Node Clean Architecture — Frameworks
  • A estrutura do aplicativo da Web é implementada pelo NestJs (criado no express).
  • Os serviços de banco de dados são implementados usando o mangusto.
  • Os serviços de CRM são um serviço simulado simples.

Implementação de serviços de dados

O código para o Repositório Genérico Mongo:

  • O repositório implementa nossa classe abstrata IGenericRepository usando o mongoose.
  • T representam uma entidade db, cada entidade tem toda a funcionalidade esperada.
  • MongoDataServices implementa a classe abstrata IDataServices.
  • Expõe conforme necessário 3 repositórios, um para cada entidade.
  • Você pode verificar a documentação do NestJs para entender melhor a implementação do mongo, que está fora do escopo deste artigo.

Serviços de CRM:

Injeção de dependência

Como discutimos antes, nossos casos de uso dependem de contratos em vez de implementação. Esses contratos precisam ser atendidos por meio de injeção de dependência em tempo de execução.

  • Crie uma nova classe de serviços de dados e repositórios que usem postgresql e siga o resumo de serviços de dados.
  • No arquivo do módulo, diga ao Nest.js para usar nossos novos serviços de dados postgresql

Resumo

Neste artigo, demonstramos como construir uma estrutura robusta de serviços camada por camada que desvincula nossa lógica principal de negócios das estruturas.

--

--

Software Enginner | Next.js | NestJs | React Native | Flutter | DevOps

Love podcasts or audiobooks? Learn on the go with our new app.

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store
Newerton Vargas de Araujo

Newerton Vargas de Araujo

Software Enginner | Next.js | NestJs | React Native | Flutter | DevOps