Simplificando a Arquitetura Hexagonal com Ruby

A Arquitetura Hexagonal é um padrão muito poderoso e embora seu nome pareça sugerir algo complexo, ela pode ser mais simples do que parece. A arquitetura implementa o padrão Ports and Adapters, nome que pode deixar mais claro como ela funciona, o padrão consiste nos conceitos de portas que fornecem…

A Arquitetura Hexagonal é um padrão muito poderoso e embora seu nome pareça sugerir algo complexo, ela pode ser mais simples do que parece. A arquitetura implementa o padrão Ports and Adapters, nome que pode deixar mais claro como ela funciona, o padrão consiste nos conceitos de portas que fornecem acesso ao centro da aplicação, como regras de negócio, e os adaptadores que ajustam os consumidores para o uso das portas.

O nome Arquitetura Hexagonal se dá somente para exemplificar que a aplicação pode ser acessada por inúmeros clientes externos (não necessariamente seis) e também gerar saídas a inúmeros agentes externos, como banco de dados, APIs externas, etc. Seu objetivo é justamente separar o core da sua aplicação do mundo exterior.

Vamos a um exemplo prático numa aplicação web comum feita em Ruby on Rails:

  • O ProdutosController recebe uma requisição, adapta os parâmetros recebidos em produto_attributes e define o repositório de persistência para a porta que contém a regra de negócio
  • A porta Produtos::Creation realiza a operação e retorna um resultado sem saber o que será retornado pelo controller ou qualquer outro consumidor
  • O controller recebe o resultado e adapta a saída que ele precisa exibindo uma mensagem no retorno da requisição.
class ProdutosController < ApplicationController
  def create
    repo = Produto::ActiveRecordRepository.new

    produto_attributes = {
      descricao: produto_params[:description],
      qtde: produto_params[:quantity],
      valor: produto_params[:price],
    }

    produto = Produtos::Creation.new(repository: repo, produto_attributes:).call

    if produto.persisted?
      render status: :created
    else
      render status: :unprocessable_entity, json: produto.errors.full_messages.to_sentence
    end
  end
end

A porta Produtos::Creation é quem chama o método create do repository que pode ser de qualquer tipo por conta da inversão de dependência.

module Produto
  class Creation
    def initialize(repository:, produto_attributes:)
      @repository = repository
      @produto_attributes = produto_attributes
    end

    def call
      @repository.create(produto_attributes)
    end
  end
end

Acabamos saindo um pouco do padrão ActiveRecord do Rails ao implementar o repository, isso foi necessário, pois o banco de dados na Arquitetura Hexagonal é considerado um serviço externo da aplicação, sendo considerado um Driven Adapter.

A implementação do repository pode ser feita de diversas formas, aqui segue uma forma mais simples:

module Produto
  class ActiveRecordRepository
    def find_all
      Produto::Record.all
    end

    def create(produto_attributes)
      Produto::Record.create(produto_attributes)
    end
  end
end

Toda a lógica das queries são implementadas na classe Repository, nesse caso estamos usando o Produto::Record como auxílio, pois possui o acesso ao banco de dados pelo ActiveRecord.

module Produto
  class Record < ApplicationRecord
    self.table_name = "produtos"

    validates :descricao, :qtde, :valor, presence: true
  end
end

Uma vantagem de usar o padrão repository é que podemos criar diversas implementações diferentes de repositórios como plugins adaptadores, o que facilita a mudança para ambientes de testes ou até uma migração de banco de dados. Segue um exemplo de um repository em memória:

module Produto
  class MemoryRepository
    def find_all
      records
    end

    def create(produto_attributes)
      records << produto_attributes
    end

    private

    def records
      @records ||= []
    end
  end
end


Por que implementar Ports and Adapters?

  • O núcleo da sua aplicação que contém as regras de negócio fica independente dos clientes externos.
  • Sua aplicação fica mais independente de bibliotecas e frameworks, tornando mais fácil a troca desses a qualquer momento.
  • Promove mais reusabilidade, pois a mesma porta, poderá ser usada em diversos contextos, como um endpoint que retorna http e em outro que é uma API com retorno em JSON, em outro que é um console, etc.
  • Facilita a testabilidade, pois os adaptadores podem ser trocados por mocks com grande facilidade.
  • A Arquitetura Hexagonal é usada como base em outras arquiteturas de software como a Clean Architecture, então se planeja futuramente implementar essa arquitetura, as mudanças serão poucas.

Referências

https://alistair.cockburn.us/hexagonal-architecture/

https://medium.com/bemobi-tech/ports-adapters-architecture-ou-arquitetura-hexagonal-b4b9904dad1a

https://www.happycoders.eu/software-craftsmanship/hexagonal-architecture/

0 Comentário