diff --git a/.gitignore b/.gitignore index b04a8c8..3336443 100644 --- a/.gitignore +++ b/.gitignore @@ -9,3 +9,6 @@ # rspec failure tracking .rspec_status + +# Debug +.byebug_history diff --git a/Gemfile.lock b/Gemfile.lock index 0e91963..c5f3e68 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -2,12 +2,40 @@ PATH remote: . specs: event_router (0.1.0) + activejob GEM remote: https://rubygems.org/ specs: + activejob (6.0.3.2) + activesupport (= 6.0.3.2) + globalid (>= 0.3.6) + activesupport (6.0.3.2) + concurrent-ruby (~> 1.0, >= 1.0.2) + i18n (>= 0.7, < 2) + minitest (~> 5.1) + tzinfo (~> 1.1) + zeitwerk (~> 2.2, >= 2.2.2) + byebug (11.1.3) + coderay (1.1.3) + concurrent-ruby (1.1.7) + connection_pool (2.2.3) diff-lcs (1.3) + globalid (0.4.2) + activesupport (>= 4.2.0) + i18n (1.8.5) + concurrent-ruby (~> 1.0) + method_source (1.0.0) + minitest (5.14.1) + pry (0.13.1) + coderay (~> 1.1) + method_source (~> 1.0) + pry-byebug (3.9.0) + byebug (~> 11.0) + pry (~> 0.13.0) + rack (2.2.3) rake (12.3.3) + redis (4.2.1) rspec (3.9.0) rspec-core (~> 3.9.0) rspec-expectations (~> 3.9.0) @@ -21,14 +49,24 @@ GEM diff-lcs (>= 1.2.0, < 2.0) rspec-support (~> 3.9.0) rspec-support (3.9.1) + sidekiq (6.1.1) + 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) + zeitwerk (2.4.0) PLATFORMS ruby DEPENDENCIES event_router! + pry-byebug rake (~> 12.0) rspec (~> 3.0) + sidekiq BUNDLED WITH 2.1.4 diff --git a/event_router.gemspec b/event_router.gemspec index 48a21af..0b1c8b1 100644 --- a/event_router.gemspec +++ b/event_router.gemspec @@ -24,4 +24,11 @@ Gem::Specification.new do |spec| spec.bindir = "exe" spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) } spec.require_paths = ["lib"] + + # Dependencies + spec.add_dependency "activejob" + + # Development dependencies + spec.add_development_dependency "sidekiq" + spec.add_development_dependency "pry-byebug" end diff --git a/lib/event_router.rb b/lib/event_router.rb index 4fb044f..5aa405d 100644 --- a/lib/event_router.rb +++ b/lib/event_router.rb @@ -1,6 +1,33 @@ require "event_router/version" +require "event_router/error" +require "active_job" +require "event_router/destination" +require "event_router/event" +require "event_router/event_serializer" +require "event_router/deliver_event_job" + +require "examples/order_placed" + +require "pry" module EventRouter - class Error < StandardError; end - # Your code goes here... + module_function + + def run_example + ActiveJob::Serializers.add_serializers EventSerializer + + Examples::OrderPlaced.publish(order_id: 1) + end + + def publish(event) + DeliverEventJob.perform_later(:notifications, event) + end + + # def publish(events) + # events = Array(events) + # + # events.map(&:destinations).flatten.each do |destination| + # DeliverEventJob.perform_later(destination, event: event) + # end + # end end diff --git a/lib/event_router/deliver_event_job.rb b/lib/event_router/deliver_event_job.rb new file mode 100644 index 0000000..d5459f3 --- /dev/null +++ b/lib/event_router/deliver_event_job.rb @@ -0,0 +1,14 @@ +# frozen_string_literal: true + +require "pry" + +module EventRouter + class DeliverEventJob < ActiveJob::Base + self.queue_adapter = :sidekiq + + def perform(destination, event) + binding.pry + event.destinations[destination]&.process(event) + end + end +end diff --git a/lib/event_router/destination.rb b/lib/event_router/destination.rb new file mode 100644 index 0000000..e28ed20 --- /dev/null +++ b/lib/event_router/destination.rb @@ -0,0 +1,19 @@ +# frozen_string_literal: true + +module EventRouter + class Destination + attr_reader :name, :handler, :handler_method + + def initialize(name, handler:, method: nil) + @name = name + @handler = handler + @handler_method = method + end + + def process(event) + @handler_method ||= event.underscore_name + + handler.send(handler_method, event) + end + end +end diff --git a/lib/event_router/error.rb b/lib/event_router/error.rb new file mode 100644 index 0000000..02a0446 --- /dev/null +++ b/lib/event_router/error.rb @@ -0,0 +1,6 @@ +# frozen_string_literal: true + +module EventRouter + class Error < StandardError; end +end + diff --git a/lib/event_router/event.rb b/lib/event_router/event.rb new file mode 100644 index 0000000..59e053b --- /dev/null +++ b/lib/event_router/event.rb @@ -0,0 +1,49 @@ +# frozen_string_literal: true + +require "securerandom" + +module EventRouter + class Event + attr_reader :uid, :correlation_id, :created_at, :payload + + class_attribute :destinations, default: {}, instance_writer: false + + def initialize(uid: SecureRandom.uuid, correlation_id: SecureRandom.uuid, created_at: Time.current, **payload) + @uid = uid + @correlation_id = correlation_id + @created_at = created_at + @payload = payload + end + + def to_hash + { + uid: uid, + correlation_id: correlation_id, + payload: payload, + created_at: created_at, + _er_klass: self.class.name + } + end + + def underscore_name + self.class.name.demodulize.underscore + end + + class << self + def inherited(base) + base.destinations = destinations.dup + super + end + + def deliver_to(name, opts = {}) + destinations[name] = EventRouter::Destination.new(name, **opts) + end + + def publish(**attrs) + event = new(**attrs) + + EventRouter.publish(event) + end + end + end +end diff --git a/lib/event_router/event_serializer.rb b/lib/event_router/event_serializer.rb new file mode 100644 index 0000000..a1879d6 --- /dev/null +++ b/lib/event_router/event_serializer.rb @@ -0,0 +1,29 @@ +# frozen_string_literal: true + +module EventRouter + class EventSerializer < ActiveJob::Serializers::ObjectSerializer + def serialize(event) + event_attrs = event.to_hash + payload = event_attrs.delete(:payload) + serialized_payload = ActiveJob::Arguments.serialize(payload) + serialized_event = super(event_attrs) + + serialized_event.merge(payload: serialized_payload) + end + + def deserialize(hash) + hash["_er_klass"].constantize.new( + uid: hash["uid"], + correlation_id: hash["correlation_id"], + created_at: Time.parse(hash["created_at"]), + **ActiveJob::Arguments.deserialize(hash["payload"]).to_h + ) + end + + private + + def klass + Event + end + end +end diff --git a/lib/examples/event_store/order_placed.rb b/lib/examples/event_store/order_placed.rb new file mode 100644 index 0000000..6505a52 --- /dev/null +++ b/lib/examples/event_store/order_placed.rb @@ -0,0 +1,14 @@ +# frozen_string_literal: true + +module EventRouter + module Examples + module EventStore + class OrderPlaced + def self.handle(event) + binding.pry + true + end + end + end + end +end diff --git a/lib/examples/notifications.rb b/lib/examples/notifications.rb new file mode 100644 index 0000000..a20a176 --- /dev/null +++ b/lib/examples/notifications.rb @@ -0,0 +1,14 @@ +# frozen_string_literal: true + +module EventRouter + module Examples + module Notifications + module_function + + def order_placed(event) + binding.pry + true + end + end + end +end diff --git a/lib/examples/order_placed.rb b/lib/examples/order_placed.rb new file mode 100644 index 0000000..e6c0dd3 --- /dev/null +++ b/lib/examples/order_placed.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +require_relative "notifications" +require_relative "event_store/order_placed" + +module EventRouter + module Examples + class OrderPlaced < EventRouter::Event + deliver_to :notifications, + handler: EventRouter::Examples::Notifications + + deliver_to :event_store, + handler: EventRouter::Examples::EventStore::OrderPlaced, + method: :handle + end + end +end diff --git a/sidekiq.yml b/sidekiq.yml new file mode 100644 index 0000000..7c81da1 --- /dev/null +++ b/sidekiq.yml @@ -0,0 +1,2 @@ +:queues: + - [default, 1]