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 emproduto_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/