From 742b8d5128db7de4c45316794d1043cc66b3c3e6 Mon Sep 17 00:00:00 2001 From: Ahmad Elassuty Date: Tue, 2 Feb 2021 20:56:18 +0100 Subject: [PATCH] feat: support custom options on event and destinations (#12) --- Gemfile.lock | 25 +++---- examples/order_placed.rb | 5 +- examples/sidekiq/config.yml | 2 + .../delivery_adapters/helpers/deliver.rb | 22 ------ .../delivery_adapters/helpers/sidekiq.rb | 6 +- lib/event_router/delivery_adapters/sidekiq.rb | 8 +- lib/event_router/destination.rb | 10 ++- lib/event_router/event.rb | 5 ++ lib/event_router/helpers/event.rb | 30 ++++++++ lib/event_router/version.rb | 2 +- spec/event_router/helpers/event_spec.rb | 74 +++++++++++++++++++ 11 files changed, 142 insertions(+), 47 deletions(-) delete mode 100644 lib/event_router/delivery_adapters/helpers/deliver.rb create mode 100644 lib/event_router/helpers/event.rb create mode 100644 spec/event_router/helpers/event_spec.rb diff --git a/Gemfile.lock b/Gemfile.lock index 1acd157..806aad0 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,28 +1,28 @@ PATH remote: . specs: - event_router (0.2.0) + event_router (0.3.0) activesupport GEM remote: https://rubygems.org/ specs: - activesupport (6.0.3.2) + activesupport (6.1.1) concurrent-ruby (~> 1.0, >= 1.0.2) - i18n (>= 0.7, < 2) - minitest (~> 5.1) - tzinfo (~> 1.1) - zeitwerk (~> 2.2, >= 2.2.2) + i18n (>= 1.6, < 2) + minitest (>= 5.1) + tzinfo (~> 2.0) + zeitwerk (~> 2.3) ast (2.4.1) byebug (11.1.3) coderay (1.1.3) - concurrent-ruby (1.1.7) + concurrent-ruby (1.1.8) connection_pool (2.2.3) diff-lcs (1.3) - i18n (1.8.5) + i18n (1.8.7) concurrent-ruby (~> 1.0) method_source (1.0.0) - minitest (5.14.1) + minitest (5.14.3) oj (3.10.14) parallel (1.19.2) parser (2.7.1.4) @@ -67,11 +67,10 @@ GEM connection_pool (>= 2.2.2) rack (~> 2.0) redis (>= 4.2.0) - thread_safe (0.3.6) - tzinfo (1.2.7) - thread_safe (~> 0.1) + tzinfo (2.0.4) + concurrent-ruby (~> 1.0) unicode-display_width (1.7.0) - zeitwerk (2.4.0) + zeitwerk (2.4.2) PLATFORMS ruby diff --git a/examples/order_placed.rb b/examples/order_placed.rb index 01c2ee1..54cebac 100644 --- a/examples/order_placed.rb +++ b/examples/order_placed.rb @@ -2,8 +2,11 @@ module Examples class OrderPlaced < EventRouter::Event + event_options queue: :orders, retry: false + deliver_to :notifications, - handler: Examples::Notifications + handler: Examples::Notifications, + queue: :notifications deliver_to :event_store, handler: Examples::EventStore::OrderPlaced, diff --git a/examples/sidekiq/config.yml b/examples/sidekiq/config.yml index 84860bb..335bc27 100644 --- a/examples/sidekiq/config.yml +++ b/examples/sidekiq/config.yml @@ -1,3 +1,5 @@ :concurrency: 1 :queues: - [event_router, 1] + - [notifications, 2] + - [orders, 2] diff --git a/lib/event_router/delivery_adapters/helpers/deliver.rb b/lib/event_router/delivery_adapters/helpers/deliver.rb deleted file mode 100644 index fd14083..0000000 --- a/lib/event_router/delivery_adapters/helpers/deliver.rb +++ /dev/null @@ -1,22 +0,0 @@ -# frozen_string_literal: true - -module EventRouter - module DeliveryAdapters - module Helpers - module Deliver - module_function - - def yield_destinations(event) - event.destinations.each do |_name, destination| - if destination.prefetch_payload? - payload = destination.extra_payload(event) - serialized_payload = EventRouter.serialize(payload) - end - - yield destination, serialized_payload - end - end - end - end - end -end diff --git a/lib/event_router/delivery_adapters/helpers/sidekiq.rb b/lib/event_router/delivery_adapters/helpers/sidekiq.rb index 1b8d3de..47d533f 100644 --- a/lib/event_router/delivery_adapters/helpers/sidekiq.rb +++ b/lib/event_router/delivery_adapters/helpers/sidekiq.rb @@ -6,12 +6,14 @@ module Helpers module Sidekiq module_function + extend EventRouter::Helpers::Event + def process_event(event, serialized_event: nil) serialized_event ||= EventRouter.serialize(event) - options = EventRouter::DeliveryAdapters::Sidekiq.options + yield_destinations(event) do |destination, serialized_payload| + options = destination_options(destination, EventRouter::DeliveryAdapters::Sidekiq) - Helpers::Deliver.yield_destinations(event) do |destination, serialized_payload| Workers::SidekiqDestinationDeliveryWorker .set(queue: options[:queue], retry: options[:retry]) .perform_async(destination.name, serialized_event, serialized_payload) diff --git a/lib/event_router/delivery_adapters/sidekiq.rb b/lib/event_router/delivery_adapters/sidekiq.rb index 49253e9..279076f 100644 --- a/lib/event_router/delivery_adapters/sidekiq.rb +++ b/lib/event_router/delivery_adapters/sidekiq.rb @@ -2,11 +2,10 @@ require 'sidekiq' -require_relative '../serializer' +require 'event_router/serializer' +require 'event_router/helpers/event' -require_relative 'helpers/deliver' require_relative 'helpers/sidekiq' - require_relative 'workers/sidekiq_destination_delivery_worker' require_relative 'workers/sidekiq_event_delivery_worker' @@ -29,7 +28,8 @@ def deliver(event) end def deliver_async(event) - serialized_event = EventRouter.serialize(event) + serialized_event = EventRouter.serialize(event) + options = EventRouter::Helpers::Event.event_options(event, self) Workers::SidekiqEventDeliveryWorker .set(queue: options[:queue], retry: options[:retry]) diff --git a/lib/event_router/destination.rb b/lib/event_router/destination.rb index d24d6f3..65e7c7c 100644 --- a/lib/event_router/destination.rb +++ b/lib/event_router/destination.rb @@ -4,7 +4,8 @@ module EventRouter class Destination # Attributes attr_reader :name, :handler, :handler_method, - :prefetch_payload, :payload_method + :prefetch_payload, :payload_method, + :options # Constants DEFAULT_ATTRIBUTES = { @@ -22,9 +23,10 @@ def initialize(name, handler:, **opts) @name = name @handler = handler - @handler_method = opts[:handler_method] - @prefetch_payload = opts[:prefetch_payload] - @payload_method = opts[:payload_method] || "#{name}_payload" + @handler_method = opts.delete(:handler_method) + @prefetch_payload = opts.delete(:prefetch_payload) + @payload_method = opts.delete(:payload_method) || "#{name}_payload" + @options = opts end def process(event, payload) diff --git a/lib/event_router/event.rb b/lib/event_router/event.rb index 9723b03..6a4e35f 100644 --- a/lib/event_router/event.rb +++ b/lib/event_router/event.rb @@ -12,6 +12,7 @@ class Event attr_accessor :correlation_id class_attribute :destinations, default: {}, instance_writer: false + class_attribute :options, instance_writer: false def initialize(uid: SecureRandom.uuid, correlation_id: SecureRandom.uuid, created_at: Time.now, **payload) @uid = uid @@ -45,6 +46,10 @@ def deliver_to(name, opts = {}) destinations[name] = EventRouter::Destination.new(name, **opts) end + def event_options(opts) + self.options = opts + end + def publish(**attrs) EventRouter.publish(new(**attrs)) end diff --git a/lib/event_router/helpers/event.rb b/lib/event_router/helpers/event.rb new file mode 100644 index 0000000..c1ca285 --- /dev/null +++ b/lib/event_router/helpers/event.rb @@ -0,0 +1,30 @@ +# frozen_string_literal: true + +module EventRouter + module Helpers + module Event + module_function + + def yield_destinations(event) + event.destinations.each do |_name, destination| + if destination.prefetch_payload? + payload = destination.extra_payload(event) + serialized_payload = EventRouter.serialize(payload) + end + + yield destination, serialized_payload if block_given? + end + end + + def event_options(event, adapter) + return adapter.options unless event.options? + + adapter.options.merge(event.options) + end + + def destination_options(destination, adapter) + adapter.options.merge(destination.options) + end + end + end +end diff --git a/lib/event_router/version.rb b/lib/event_router/version.rb index d7d78d3..77244c2 100644 --- a/lib/event_router/version.rb +++ b/lib/event_router/version.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true module EventRouter - VERSION = '0.2.0' + VERSION = '0.3.0' end diff --git a/spec/event_router/helpers/event_spec.rb b/spec/event_router/helpers/event_spec.rb new file mode 100644 index 0000000..8981f77 --- /dev/null +++ b/spec/event_router/helpers/event_spec.rb @@ -0,0 +1,74 @@ +require 'event_router/helpers/event' + +RSpec.describe EventRouter::Helpers::Event do + describe '.yield_destinations' do + subject { described_class.yield_destinations(event) } + + let(:event) { DummyEvent.new } + + let(:expected_args) do + event.destinations.map { |_, destination| [destination, anything] } + end + + it 'yields the event destinations with serialized payload' do + expect { |b| described_class.yield_destinations(event, &b) }.to yield_successive_args(*expected_args) + end + + it 'serializes the payload' do + expect(EventRouter).to receive(:serialize).once + + subject + end + end + + describe '.event_options' do + subject { described_class.event_options(event, adapter) } + + let(:event) { DummyEvent.new } + let(:adapter) { DummyDeliveryAdapter } + let(:adapter_options) { { option_1: :adapter, option_2: :adapter } } + + before { allow(adapter).to receive(:options) { adapter_options } } + + context 'when event has custom options' do + let(:event_options) { { option_1: :event } } + + before { allow(event).to receive(:options) { event_options } } + + it 'merges the event options to the adapter options' do + is_expected.to eq(option_1: :event, option_2: :adapter) + end + end + + context 'when event does not have any custom options' do + it 'returns the adapter options' do + is_expected.to eq(adapter_options) + end + end + end + + describe '.destination_options' do + subject { described_class.destination_options(destination, adapter) } + + let(:adapter) { DummyDeliveryAdapter } + let(:adapter_options) { { option_1: :adapter, option_2: :adapter } } + + before { allow(adapter).to receive(:options) { adapter_options } } + + context 'when destination has custom options' do + let(:destination) { EventRouter::Destination.new(:notifications, handler: :any, option_1: :destination) } + + it 'merges the destination options to the adapter options' do + is_expected.to eq(option_1: :destination, option_2: :adapter) + end + end + + context 'when destination does not have any custom options' do + let(:destination) { EventRouter::Destination.new(:notifications, handler: :any) } + + it 'returns the adapter options' do + is_expected.to eq(adapter_options) + end + end + end +end