diff --git a/aggregate_root/lib/aggregate_root.rb b/aggregate_root/lib/aggregate_root.rb index 7a520a4d81..1b327a417f 100644 --- a/aggregate_root/lib/aggregate_root.rb +++ b/aggregate_root/lib/aggregate_root.rb @@ -13,7 +13,7 @@ module OnDSL def on(*event_klasses, &block) event_klasses.each do |event_klass| - name = event_type_for(event_klass) + name = event_klass.to_s raise(ArgumentError, "Anonymous class is missing name") if name.start_with? ANONYMOUS_CLASS handler_name = "on_#{name}" @@ -76,15 +76,38 @@ def marshal_load(vars) end end - def self.with(strategy: -> { DefaultApplyStrategy.new }, event_type_resolver: ->(value) { value.to_s }) + RubyEventStore::Deprecations.register( + :aggregate_root_event_type_resolver, + <<~EOW, + Passing event_type_resolver to AggregateRoot has been deprecated. + + Event type is now derived from event.event_type. The event_type_resolver + argument is ignored and will be removed in a future release. + EOW + ) + + RubyEventStore::Deprecations.register( + :aggregate_root_event_type_for, + <<~EOW, + Calling event_type_for on an AggregateRoot class has been deprecated. + + Event type is now derived from event.event_type. This method is ignored + internally, returns value.to_s, and will be removed in a future release. + EOW + ) + + def self.with(strategy: -> { DefaultApplyStrategy.new }, event_type_resolver: nil) Module.new do define_singleton_method :included do |host_class| host_class.extend Constructor host_class.extend OnDSL if strategy.call.respond_to?(:uses_on_dsl?) host_class.include AggregateMethods - host_class.define_singleton_method :event_type_for do |value| - event_type_resolver.call(value) - end + host_class.define_singleton_method(:event_type_for) { |value| value.to_s } + RubyEventStore::Deprecations.deprecate_class_method( + host_class, + :event_type_for, + key: :aggregate_root_event_type_for, + ) end define_method :apply_strategy do @@ -96,4 +119,15 @@ def self.with(strategy: -> { DefaultApplyStrategy.new }, event_type_resolver: -> def self.included(host_class) host_class.include with end + + DeprecatedEventTypeResolver = + Module.new do + def with(event_type_resolver: nil, **) + unless event_type_resolver.nil? + RubyEventStore::Deprecations.warn(:aggregate_root_event_type_resolver) + end + super + end + end + singleton_class.prepend(DeprecatedEventTypeResolver) end diff --git a/aggregate_root/lib/aggregate_root/default_apply_strategy.rb b/aggregate_root/lib/aggregate_root/default_apply_strategy.rb index a41d5d8c3c..944d0a800a 100644 --- a/aggregate_root/lib/aggregate_root/default_apply_strategy.rb +++ b/aggregate_root/lib/aggregate_root/default_apply_strategy.rb @@ -1,5 +1,7 @@ # frozen_string_literal: true +require "ruby_event_store/deprecations" + module AggregateRoot MissingHandler = Class.new(StandardError) NullHandler = Proc.new {} diff --git a/aggregate_root/spec/aggregate_root_spec.rb b/aggregate_root/spec/aggregate_root_spec.rb index a395f25f5d..3dd3a21cd6 100644 --- a/aggregate_root/spec/aggregate_root_spec.rb +++ b/aggregate_root/spec/aggregate_root_spec.rb @@ -1,6 +1,8 @@ # frozen_string_literal: true require "spec_helper" +require "active_support" +require "active_support/notifications" ::RSpec.describe AggregateRoot do let(:uuid) { SecureRandom.uuid } @@ -180,6 +182,77 @@ def custom_expired(_event) expect(klass.respond_to?(:on_methods)).to eq(true) end + it "deprecates passing event_type_resolver to AggregateRoot.with" do + expect { AggregateRoot.with(event_type_resolver: ->(value) { value.to_s }) }.to output(<<~EOS).to_stderr + [DEPRECATION] Passing event_type_resolver to AggregateRoot has been deprecated. + + Event type is now derived from event.event_type. The event_type_resolver + argument is ignored and will be removed in a future release. + EOS + end + + it "does not deprecate when event_type_resolver is not passed to AggregateRoot.with" do + expect { AggregateRoot.with }.not_to output.to_stderr + end + + it "emits the AggregateRoot.with deprecation under a suppressible key" do + RubyEventStore::Deprecations.suppress(:aggregate_root_event_type_resolver) + + expect { AggregateRoot.with(event_type_resolver: ->(value) { value.to_s }) }.not_to output.to_stderr + end + + it "routes apply by event.event_type, ignoring a passed event_type_resolver" do + klass = nil + silence_warnings do + klass = + Class.new do + include AggregateRoot.with(event_type_resolver: ->(value) { "prefixed.#{value}" }) + + def initialize + @status = :draft + end + + attr_accessor :status + + on Orders::Events::OrderCreated do |_event| + @status = :created + end + end + end + order = klass.new + + order.apply(Orders::Events::OrderCreated.new) + + expect(order.status).to eq(:created) + end + + it "deprecates calling event_type_for on an aggregate class" do + klass = Class.new { include AggregateRoot } + + expect { klass.event_type_for(Orders::Events::OrderCreated) }.to output(<<~EOS).to_stderr + [DEPRECATION] Calling event_type_for on an AggregateRoot class has been deprecated. + + Event type is now derived from event.event_type. This method is ignored + internally, returns value.to_s, and will be removed in a future release. + EOS + end + + it "event_type_for returns value.to_s" do + klass = Class.new { include AggregateRoot } + result = nil + + silence_warnings { result = klass.event_type_for(Orders::Events::OrderCreated) } + + expect(result).to eq("Orders::Events::OrderCreated") + end + + it "emits the event_type_for deprecation under a suppressible key" do + RubyEventStore::Deprecations.suppress(:aggregate_root_event_type_for) + klass = Class.new { include AggregateRoot } + + expect { klass.event_type_for(Orders::Events::OrderCreated) }.not_to output.to_stderr + end + it "included modules" do klass = Class.new { include AggregateRoot } diff --git a/ruby_event_store/lib/ruby_event_store/client.rb b/ruby_event_store/lib/ruby_event_store/client.rb index 4e243a097c..d4e633b8ad 100644 --- a/ruby_event_store/lib/ruby_event_store/client.rb +++ b/ruby_event_store/lib/ruby_event_store/client.rb @@ -64,7 +64,7 @@ def publish(events, topic: nil, stream_name: GLOBAL_STREAM, expected_version: :a enriched_events.zip(records) do |event, record| with_metadata(correlation_id: event.metadata.fetch(:correlation_id), causation_id: event.event_id) do if @broker.public_method(:call).arity == 3 - @broker.call(topic || event.event_type, event, record) + @broker.call(topic || @event_type_resolver.call(event.class), event, record) else warn <<~EOW Message broker shall support topics. diff --git a/ruby_event_store/lib/ruby_event_store/event.rb b/ruby_event_store/lib/ruby_event_store/event.rb index 6e9a3bdad1..45ca01f2cf 100644 --- a/ruby_event_store/lib/ruby_event_store/event.rb +++ b/ruby_event_store/lib/ruby_event_store/event.rb @@ -27,7 +27,10 @@ def message_id event_id end - # Type of event. Used when matching with subscribed handlers. + # Type of event, derived from metadata[:event_type] or the class name. + # Used by the AggregateRoot apply strategy to look up on-handlers. + # Note: RubyEventStore dispatch and subscriptions route via the + # event_type_resolver, not this value. # @return [String] def event_type metadata[:event_type] || self.class.name diff --git a/ruby_event_store/spec/client_spec.rb b/ruby_event_store/spec/client_spec.rb index 97b927873f..1f7e2f0deb 100644 --- a/ruby_event_store/spec/client_spec.rb +++ b/ruby_event_store/spec/client_spec.rb @@ -1006,6 +1006,26 @@ def event_id expect(event.event_id).to eq("8d69cc2b-c6c5-4494-99f6-954c7f583477") end + specify "custom resolver output differing from event.event_type still routes published events to subscribers" do + received = [] + resolver = ->(klass) { "prefixed.#{klass}" } + client = Client.new(event_type_resolver: resolver) + client.subscribe(to: [OrderCreated]) { |event| received << event } + + client.publish(order_created = OrderCreated.new) + + expect(received).to eq([order_created]) + end + + specify "dispatch routes by event_type_resolver output, not by metadata[:event_type]" do + received = [] + client.subscribe(to: [OrderCreated]) { |event| received << event } + + client.publish(order_created = OrderCreated.new(metadata: { event_type: "Some.Other.Type" })) + + expect(received).to eq([order_created]) + end + describe "#position_in_stream" do specify do client.publish(fact0 = OrderCreated.new, expected_version: :auto, stream_name: "SomeStream")