Modelo de Desing de Aplicações Backend
Bruno Brolesi
Posted on June 20, 2024
Índice
Introdução
Após anos trabalhando com diferentes modelos de design em aplicações e coletando feedbacks variados sobre como as implementações evoluem ao longo do tempo, decidi escrever este artigo. Aqui, proponho um modelo de design de código para o desenvolvimento de aplicações backend, baseado no que funcionou melhor nas equipes em que trabalhei. Esse modelo busca equilibrar abstrações e a separação de responsabilidades, combinando conceitos de Arquitetura Hexagonal, Clean Architecture e DDD.
A imagem abaixo oferece um panorama do modelo que será detalhado ao longo deste artigo.
Antes de nos aprofundarmos nos detalhes de cada módulo, vejamos um panorama rápido das camadas da nossa aplicação:
Um cliente faz uma chamada para a aplicação, que é recebida na camada de
delivery
. Nessa etapa, os dados fornecidos são extraídos e validados. Se os dados forem inválidos, a requisição é rejeitada aqui mesmo.Se os dados forem válidos, a requisição é encaminhada para a camada de
usecase
, onde todas as regras de negócio são processadas.Se o
usecase
precisar de um serviço externo, como um banco de dados, ele utiliza a camada degateway
, que contém as interfaces que definem os contratos das implementações responsáveis por essa comunicação.Após a interação com o serviço externo, o fluxo segue o caminho inverso até retornar o resultado ao cliente, passando novamente pela camada de
usecase
e depois pela camada dedelivery
.
Camadas
Agora vamos explorar cada uma das camadas do modelo mostrado na imagem, começando pelas partes mais externas: infrastructure
e business
.
Infrastructure
infrastructure
: Este pacote contém tudo o que não faz parte das regras de negócio da aplicação, como protocolos de comunicação, frameworks e conexões com bancos de dados. Vamos explorar os pacotes internos deste módulo:
-
delivery
: Responsável pela comunicação entre a aplicação e seus clientes (webapp, CLI, etc.). Ele possui subpacotes para organizar cada responsabilidade.-
webapp
: Contém o servidor web, rotas e o tratamento de requisições HTTP. -
middlewares
: Contém os middlewares da aplicação. -
handlers
: Trata as requisições HTTP, extraindo e validando os dados, para então chamar osuse cases
. -
consumers
: Trata as mensagens recebidas em consumers, realizando a extração e validação dos dados antes de chamar osuse cases
. -
requests
: Define as estruturas e validadores para o body, headers, pathParams e queryParams das requisições. -
responses
: Define as estruturas e padroniza as respostas de sucesso e erro dos handlers. -
messages
: Define e valida as mensagens processadas pelos consumers. -
dependencies
: Gerencia a injeção de dependências da aplicação.
-
-
repository
: Implementa a comunicação com bancos de dados, seguindo o contrato definido pelos gateways.-
config
: Configura os clientes dos bancos de dados utilizados no repository.
-
-
service
: Implementa a comunicação com serviços externos, seguindo o contrato definido pelos gateways.-
config
: Configura clientes HTTP, SDKs, etc., utilizados pelo service.
-
-
publisher
: Implementa a publicação de mensagens em tópicos, seguindo o contrato dos gateways.-
config
: Configura os clientes utilizados para publicação de mensagens.
-
Business
business
: Este pacote contém tudo relacionado ao domínio da aplicação, dividido nos seguintes subpacotes:
-
usecase
: Implementa os casos de uso da aplicação, aplicando as regras de negócio necessárias. -
logic
: Camada opcional para tratar lógicas mais complexas, como a aplicação do padrão strategy e chain-of-responsibility. -
gateway
: Define as interfaces (contratos) para comunicação com serviços externos, que são implementadas nas camadas derepository
,service
epublisher
. -
domain
: Contém as entidades core da aplicação. A camadadomain
pode ser usada por outras camadas, mas não deve depender de nenhuma delas.
Boas Práticas
Testes Unitários
A escrita de testes unitários é essencial, mas não para todas as camadas. Em minha experiência, não é vantajoso testar pacotes de infrastructure
que implementam os gateways (repository
, service
e publisher
), pois, em geral, esses pacotes utilizam bibliotecas externas já bem testadas.
Ao escrever testes unitários, devemos isolar a unidade de código. Por exemplo, ao testar um handler
, o usecase
chamado deve ser mockado. Da mesma forma, ao testar um usecase
, as chamadas aos serviços externos devem ser mocks, não implementações reais.
Testes de Integração
Testes de integração garantem a confiabilidade da comunicação entre camadas. Eles são especialmente úteis para cobrir partes da aplicação que não possuem testes unitários. Ao realizar esses testes, simulamos a aplicação e mockamos apenas as dependências externas, como bancos de dados ou chamadas HTTP.
Fluxo de Desenvolvimento
Uma boa prática é evitar criar toda a estrutura do projeto no início. Em vez disso, comece pela camada de usecase
, e vá adicionando as outras camadas conforme necessário. Isso mantém o foco nas regras de negócio e evita código não utilizado.
Dica: Utilizar diagramas de fluxo, como os criados com o Mermaid, ajuda a visualizar o que precisa ser implementado em cada usecase
.
Pontos de Atenção
Este modelo separa bem as regras de negócio das tecnologias de terceiros, mas, em alguns casos, pode ser necessário "sujar" o design para atender a certos requisitos, como a necessidade de uma transação SQL. O importante é balancear eficiência e elegância no design.
Posted on June 20, 2024
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.
Related
November 29, 2024