profile
viewpoint
Andrzej Sliwa andrzejsliwa zencargo $GOPATH https://medium.com/andrzej-%C5%9Bliwa Holistic CTO/VP Engineering, Polyglot Developer. Event Sourcing, Domain Driven Design & Event Storming Practitioner

andrzejsliwa/backbone-couch 65

CouchDB connector for Backbone.js

andrzejsliwa/AceJump-Lite 2

simplify from emacIDEAs

andrzejsliwa/.bash 0

handle bash configs in more pragmatic way

andrzejsliwa/64spec 0

6502/Commodore64 Testing Framework for KickAssembler

andrzejsliwa/64spec-cli 0

A working prototype of a command line runner for 64spec

andrzejsliwa/64tass 0

64tass - cross assembler for 6502 etc. microprocessors - by soci/singular - [git clone from the original sourceforge repo]

andrzejsliwa/AceJump 0

A single char search, select, and jump

andrzejsliwa/alchemist.el 0

Elixir Tooling Integration Into Emacs

fork andrzejsliwa/clojure

The Clojure programming language

https://clojure.org

fork in a month

fork andrzejsliwa/kogito-examples

Kogito examples - Kogito is a cloud-native business automation technology for building cloud-ready business applications.

http://kogito.kie.org

fork in a month

issue openedRailsEventStore/rails_event_store

Discussion: Fault tolerance for errors on event handlers

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

created time in 3 months

issue openedRailsEventStore/rails_event_store

Discussion: Support for Refactoring use case for Events

We have here few use cases:

  1. Renaming / moving event class
  2. Modification of existing event (ex. adding new attribute)

In order to support both use case we have done example customizations:

  1. Renaming / moving event class

In such use case we had to implement custom repository:

module Infra
  # Clone of RailsEventStoreActiveRecord::EventRepository
  # with changes:
  #   - constructor is accepting 'events_name_mapping' keyword with hash
  #     configuration of renaming mapping
  #   - with modified version of build_event_entity method, which is using this mapping
  #   - with modified version of read_all_streams_forward method, with support for event_types
  #     (used in rebuilding of read models, to reduce scope)
  class EventRepository < RailsEventStoreActiveRecord::EventRepository
    def initialize(events_rename_mapping: {})
      @events_rename_mapping = events_rename_mapping
      super()
    end

    # improved version, please take a look documentation on top
    def read_all_streams_forward(start_event_id, count, event_types)
      stream = adapter
      unless start_event_id.equal?(:head)
        starting_event = adapter.find_by(event_id: start_event_id)
        stream = stream.where('id > ?', starting_event)
      end

      scope = stream.order('id ASC')
      scope = scope.where(event_type: event_types) if event_types
      scope = scope.limit(count)
      scope.map(&method(:build_event_entity))
    end

    private

    attr_reader :events_rename_mapping

    # improved version, please take a look documentation on top
    def build_event_entity(record)
      return nil unless record
      event_type = events_rename_mapping.fetch(record.event_type) { record.event_type }
      event_type.constantize.new(
        event_id: record.event_id,
        metadata: record.metadata,
        data:     record.data
      )
    end
  end
end

Configure using of this repository by our configuration:

EVENTS_RENAME_MAPPING = {
  # Example mapping in case of refactoring (move or rename)
  "Loans::Events::LoanGivenEvent" => "Loans::Events::SomeGivenEvent"
}

class CoreConfiguration
  def initialize(repository: Infra::EventRepository.new(events_rename_mapping: EVENTS_RENAME_MAPPING),
                 event_store: RailsEventStore::Client.new(repository: repository),
                 command_bus: Infra::CommandBus.new,
                 command_injector: Infra::CommandInjector.new(command_bus: command_bus))
    @event_store      = event_store
    @command_bus      = command_bus
    @command_injector = command_injector

    configure_aggregate_root(event_store)
    setup_event_handler_strategy
    setup_read_models(event_store)
    register_event_handlers(event_store)
    register_command_handlers(command_bus)
  end

  def configure_aggregate_root(event_store)
    AggregateRoot.configure do |config|
      config.default_event_store = event_store
    end
  end
  ...

  attr_reader :event_store,
              :command_bus,
              :all_command_handlers,
              :all_read_models,
              :all_event_handlers,
              :command_injector
end
  1. modification of existing event (ex. adding new attribute, changing type of existing one, etc)
module Loans
  module Events
    class LoanGivenEvent < ::Infra::Event
      version 4

      attribute :loan_number,     Loans::Types::LoanNumber
      attribute :loan_conditions, Loans::Types::LoanConditions

      def self.convert_from_v1_to_v2(event)
        puts "\n#{self} convert: v1 -> v2\n"
        puts event.inspect
        event
      end

      def self.convert_from_v2_to_v3(event)
        puts "\n#{self} convert: v2 -> v3\n"
        puts event.inspect
        event
      end

      def self.convert_from_v3_to_v4(event)
        puts "\n#{self} convert: v3 -> v4\n"
        puts event.inspect
        event
      end
    end
  end
end

by default version is equal 1 implicitly:

module Loans
  module Events
    class LoanGivenEvent < ::Infra::Event
      attribute :loan_number,     Loans::Types::LoanNumber
      attribute :loan_conditions, Loans::Types::LoanConditions
    end
  end
end
module Infra
  class Event < Dry::Struct
    include ::Base::Contracts

    class VersionConverter
      include ::Base::Contracts

      SpecEventAttributes = KeywordArgs[event_id: String, data: Hash, metadata: Hash]

      Contract SpecEventAttributes, ClassOf[Event] => SpecEventAttributes
      def call(event_attributes, klass)
        event_version     = event_version(event_attributes, klass)
        outdated_versions = (event_version..(klass.version - 1)).to_a
        outdated_versions.inject(event_attributes) do |attributes, current_version|
          upgrade(klass, attributes, from: current_version)
        end
      end

      private

      Contract SpecEventAttributes, Any => Integer
      def event_version(event_attributes, _klass)
        event_attributes.dig(:metadata, :version) || INITIAL_VERSION
      end
   

      Contract ClassOf[Event], SpecEventAttributes, KeywordArgs[from: Integer] => SpecEventAttributes
      def upgrade(klass, event_attributes, from:)
        event_version = from
        upgraded_event_version = event_version + 1
        new_data =
          klass
            .method("convert_from_v#{event_version}_to_v#{upgraded_event_version}")
            .call(event_attributes[:data])
        event_attributes
          .merge(data: new_data)
          .deep_merge(metadata: { version: upgraded_event_version })
          .slice(:data, :metadata, :event_id)
      end
    end

    INITIAL_VERSION = 1
    Contract Or[nil, Integer] => Integer
    def self.version(version = nil)
      if version
        @version = version
      else
        @version || INITIAL_VERSION
      end
    end

    ...
  end
end

created time in 3 months

more