Receiving SQS messages with Ruby

Tony DuongTony Duong
2 min read

Recently, at work, I learned a way to handle SQS messages polled from an SQS queue in an efficient way. I will share the approach here.

# Path: app/services/football_events/receive_sqs_football_events.rb
class FootballEvents::ReceiveSQSFootballEvents
  class_attribute :handlers, default: {}

  attr_reader :queue_poller

  IDLE_TIMEOUT = 28800 # 8 hours
  QUEUE_NAME = 'football-events'

  # Register a handler for a specific key
  # @param key [String] the key to register the handler for
  # @param handler [Class] the handler class to use
  # @param option [Hash] the options to use
  # @return [void]
  def self.register_handler(key, handler, **option)
    handlers[key] = handler
  end

  register_handler FootballEvents::GOAL, FootballEvents::ProcessGoalService
  register_handler FootballEvents::FOUL, FootballEvents::ProcessFoulService
  register_handler FootballEvents::OFFSIDE, FootballEvents::ProcessOffsideService
  register_handler FootballEvents::CORNER, FootballEvents::ProcessCornerService
  register_handler FootballEvents::PENALTY_KICK, FootballEvents::PrcoessPenaltyKickService

  # Initialize the class
  # @return [void]
  def initialize
    sqs_client = Aws::SQS::Client.new
    queue_url = sqs_client.get_queue_url(queue_name: QUEUE_NAME).queue_url

    @queue_poller = Aws::SQS::QueuePoller.new(queue_url, client: sqs_client)
  end

  # Poll the queue for messages
  # @return [void]
  def poll
    @queue_poller.poll(idle_timeout: IDLE_TIMEOUT) do |message|
      body = JSON.parse(message.body)

      event_type = body['event_type']
      options = body['options']

      handler = handlers[event_type]
      handler.new(**options).call
    end
  end
end

In the above code snippet, we have a service class FootballEvents::ReceiveSQSFootballEvents that polls messages from an SQS queue named football-events. The messages are then processed by the appropriate handler based on the event_type field in the message body.

All the handlers need to implement a call method, which is called to process the message. This allows for easy extensibility and maintainability of the codebase.

# app/services/football_events/process_goal_service.rb
class FootballEvents::ProcessGoalService < FootballEvents::BaseService
  def initialize(match_id:, player_id:, time:)
    @match_id = match_id
    @player_id = player_id
    @time = time
  end

  def call
    # Process the goal event
  end
end

# app/services/football_events/process_foul_service.rb
class FootballEvents::ProcessFoulService < FootballEvents::BaseService
  def initialize(match_id:, player_id:, target_player_id:, time:)
    @match_id = match_id
    @player_id = player_id
    @target_player_id = target_player_id
    @time = time
  end

  def call
    # Process the foul event
  end
end
0
Subscribe to my newsletter

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

Written by

Tony Duong
Tony Duong

I love writing beautiful code ✨