Skip to content
Open
44 changes: 39 additions & 5 deletions aggregate_root/lib/aggregate_root.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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}"
Expand Down Expand Up @@ -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
Expand All @@ -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
2 changes: 2 additions & 0 deletions aggregate_root/lib/aggregate_root/default_apply_strategy.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
# frozen_string_literal: true

require "ruby_event_store/deprecations"

module AggregateRoot
MissingHandler = Class.new(StandardError)
NullHandler = Proc.new {}
Expand Down
73 changes: 73 additions & 0 deletions aggregate_root/spec/aggregate_root_spec.rb
Original file line number Diff line number Diff line change
@@ -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 }
Expand Down Expand Up @@ -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 }

Expand Down
2 changes: 1 addition & 1 deletion ruby_event_store/lib/ruby_event_store/client.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
5 changes: 4 additions & 1 deletion ruby_event_store/lib/ruby_event_store/event.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
20 changes: 20 additions & 0 deletions ruby_event_store/spec/client_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down
Loading