Skip to content

Discussion: Fault tolerance for errors on event handlers #1180

@andrzejsliwa

Description

@andrzejsliwa

Current implementation of PubSub Broker is not fault tolerant, which means that if one error handler will fail the "broadcasting" will stop processing event by other handlers which were added after failing one.

https://github.com/RailsEventStore/rails_event_store/blob/master/ruby_event_store/lib/ruby_event_store/pub_sub/broker.rb#L26

Because of current implementation of the broker I had to install such error isolation layer:

Configuration:

...
  def setup_event_handler_strategy
    Rails.configuration.event_handler_strategy =
      %w[development test].include?(Rails.env) ? :throw : :notify
  end
....

Usage of Error handling strategy:

module Infra
  module EventHandlers
    include Contracts::Core
    include Contracts::Builtin

    class UnknownHandlerStrategy < StandardError
    end

    Contract Config => Any
    def connect(event_handler_config)
      @configs ||= []
      @configs << event_handler_config.handlers
    end

    Contract KeywordArgs[event_store: RailsEventStore::Client,
                         event_handler_error_strategy: Optional[EventHandlerErrorStrategy]] => Any
    def register(event_store: Rails.configuration.core.event_store,
                 event_handler_error_strategy: EventHandlerErrorStrategy.new)
      configs.flatten.each do |c|
        events  = c.fetch(:events)
        handler = event_handler_error_strategy.build_error_handler(c.fetch(:handler), events)
        event_store.subscribe(handler, events)
      end
    end

    attr_reader :configs
  end
end

Implementation of Error handling strategy :

module Infra
  class EventHandlerErrorStrategy
    def initialize(event_handler_strategy: Rails.configuration.event_handler_strategy)
      @strategy = event_handler_strategy
    end

    def build_error_handler(handler, events)
      send_handler_info(handler, events)
      case strategy
      when :notify
        ->(event) { notify_error_handler(handler, event) }
      when :throw
        ->(event) { handler.call(event) }
      else
        raise UnknownHandlerStrategy
      end
    end

    private

    def send_handler_info(handler, events)
      for_events    = "  - for events: #{events.inspect}"
      with_strategy = "  - with '#{strategy}' strategy"
      message = "event handler registered #{handler}\n#{for_events}\n#{with_strategy}"
      if %w[development test].include?(Rails.env)
        puts message
      else
        Rails.logger.info message
      end
    end

    def notify_error_handler(handler, event)
      handler.call(event)
    rescue StandardError => ex
      Airbrake.notify(ex)
    end

    attr_reader :strategy
  end
end

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions