Modelo de Desing de Aplicações Backend

brunobrolesi

Bruno Brolesi

Posted on June 20, 2024

Modelo de Desing de Aplicações Backend

Índice

  1. Introdução
  2. Camadas
  3. Boas Práticas
  4. Fluxo de Desenvolvimento
  5. Pontos de Atenção

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.

Imagem contendo blocos que representam cada camada e subcamada da aplicação; cada camada será explicada ao longo do artigo

Antes de nos aprofundarmos nos detalhes de cada módulo, vejamos um panorama rápido das camadas da nossa aplicação:

  1. 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.

  2. 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.

  3. Se o usecase precisar de um serviço externo, como um banco de dados, ele utiliza a camada de gateway, que contém as interfaces que definem os contratos das implementações responsáveis por essa comunicação.

  4. 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 de delivery.

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 os use cases.
    • consumers: Trata as mensagens recebidas em consumers, realizando a extração e validação dos dados antes de chamar os use 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 de repository, service e publisher.
  • domain: Contém as entidades core da aplicação. A camada domain 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.

💖 💪 🙅 🚩
brunobrolesi
Bruno Brolesi

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