Rails Constraint - Como eles podem te ajudar?

Renan PortoRenan Porto
2 min read

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

0
Subscribe to my newsletter

Read articles from Renan Porto directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Renan Porto
Renan Porto