Rails Constraint - Como eles podem te ajudar?
Recentemente fui designado para desenvolver uma rota na nossa aplicação para receber mensagens de webhooks de múltiplas plataformas.
A minha ideia inicial era simples:
Criar uma rota para a plataforma que iria enviar a mensagem
Criar uma forma de autenticar a mensagem
- No nosso contexto, validar que a mensagem vem de uma conta cadastrada e ativa
Publicar mensagem em uma fila para ser processada posteriormente
- P.S: Esse webhook só iria ser utilizado para clientes que só possuem webhook via HTTP, como é o exemplo da VTEX e tray.
Então, meu código ficou algo semelhante com isto:
post '/webhooks/vtex/:token', , to 'vtex_webhooks#create'
class VtexWebhooks
def create
result = Platform::Vtex::WebhookPublisher.new.call(params)
if result.success?
head :ok
else
render json: { errors: result.errors }, status: :unprocessable_entity
end
end
end
class RegisterWebhook
def call
token = account.generate_token_for :webhook_key
hook_url = url_helpers.webhooks_vtex_url(token:)
# Exemplo: http://localhost:3000/webhooks/vtex/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJ
VtexWebhook.create(hook_url)
end
end
# Model Account
class Account < ApplicationRecord
generates_token_for :webhook_key
[..]
end
Após eu ter feito este código, eu pedi para que um membro do meu time revisasse esse código, e ele me deu uma dica bem interessante: usar constraints e remover a validação dos parametros (por que o consumidor das pessoas que irá ser responsável por isto).
Por que usar constraints nessa situação?
Eu estou criando uma rota para cadastrar os webhooks da vtex. Porém, e quando eu tiver mais 3 plataformas para cadastrar? Irei ter que criar mais 3 rotas? E ficar repetindo esse código? É ai que entra as constraints!
Bora refatorar esse código?
Primeiramente, alterei a rota para usar um controlador genérico chamado MessagesController
e para possuir um parametro platform
que será repassado dinamicamente, porém neste momento, terá somente a vtex disponível:
post '/:platform/:token', to: 'messages#create',
constraints: lambda { |request| Account::WEBHOOK_PLATFORMS.include?(request.params[:platform]) }
# Model Account
class Account < ApplicationRecord
WEBHOOK_PLATFORMS = %w[vtex].freeze
[...]
end
Essa rota faz com que eu possa facilmente incluir novas plataformas para utilizar essa rota, simplesmente adicionando eles na constante WEBHOOK_PLATFORMS
.
O código do controller ficou da seguinte forma:
module Webhooks
class MessagesController < ApplicationController
before_action :validate_token
def create
# Serviço que publica a mensagem
Webhooks::ReceiveMessage.new.call(platform: params[:platform], params:)
end
private
def validate_token
account = Accounts::Finder.find_by_webhook_key(params[:token])
return if account.present?
render json: AccountNotFoundByTokenPresenter.new(params[:token]).as_json,
status: :not_found
end
end
end
Ou seja, eu irei publicar a mensagem recebida pela plataforma específica no pubsub, passando o platform
como parametro para eu poder filtrar no meu consumidor posteriormente, e além disso, eu faço a validação do token, para ver se a conta realmente existe.
Sinceramente, isso foi um ajuste simples, porém necessário e que tornou o código mais legível e mais simples de se entender.
Agradeço ao mestre @hashtegner pela dica!
Referencias
https://guides.rubyonrails.org/routing.html#segment-constraints
Subscribe to my newsletter
Read articles from Renan Porto directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by