New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Instrumented wrappers could break backward compatibility #561
Comments
Hey, thanks for the report! Let me better understand the issue:
Is that correct? |
In fact Almost, because module RailsEventStore
class Client < RubyEventStore::Client
attr_reader :request_metadata
def initialize(repository: RailsEventStoreActiveRecord::EventRepository.new,
mapper: RubyEventStore::Mappers::Default.new,
subscriptions: RubyEventStore::PubSub::Subscriptions.new,
dispatcher: RubyEventStore::ComposedDispatcher.new(
RubyEventStore::ImmediateAsyncDispatcher.new(scheduler: ActiveJobScheduler.new),
RubyEventStore::PubSub::Dispatcher.new),
request_metadata: default_request_metadata)
super(repository: RubyEventStore::InstrumentedRepository.new(repository, ActiveSupport::Notifications),
mapper: RubyEventStore::Mappers::InstrumentedMapper.new(mapper, ActiveSupport::Notifications),
subscriptions: subscriptions,
dispatcher: RubyEventStore::InstrumentedDispatcher.new(dispatcher, ActiveSupport::Notifications)
)
@request_metadata = request_metadata
end
def with_request_metadata(env, &block)
with_metadata(request_metadata.call(env)) do
block.call
end
end
private
def default_request_metadata
->(env) do
request = ActionDispatch::Request.new(env)
{
remote_ip: request.remote_ip,
request_id: request.uuid
}
end
end
end
end As a short-term workaround I'd use |
The direction we'd like to take in the future with mappers is more or less here: #400, which would be composing them from smaller bits: CURRENT = Mapper.new(
Default,
SymbolizeMetadataKeys,
Serializer.new,
)
JSON = Mapper.new(
Default,
SymbolizeMetadataKeys,
Serializer.new(serializer: JSON),
) I'd love to learn about your use case more :) |
@pawelpacana
Yep.
Yeah, that was my first suggestion, but copy-pasting this single method doesn't seem to be a good idea( |
I'm using custom So, I store this custom mapping within a mapper itself (thus I have a couple more method, e.g. The pros of not using class names:
|
The easiest solution for me would be storing the mapping somewhere else. Not sure the same would work for dispatchers and, especially, repositories (I think, having custom repository API could be useful). |
Btw. default mapper supports this already, provided it is given a map of type->class def serialized_record_to_event(record)
event_type = events_class_remapping.fetch(record.event_type) { record.event_type }
Object.const_get(event_type).new(
event_id: record.event_id,
metadata: TransformKeys.symbolize(serializer.load(record.metadata)),
data: serializer.load(record.data)
)
end
Which reminded me that the idea of registering events floated around #210 and #60.
I guess it boils down to the fact the you already have an instance of RES client with its mapper instance and start registering this type -> class maps. Having a complete map before instantiating a mapper that uses such map could be one of the possibilities. Provided you don't really need to change that map at runtime. |
One thing I forgot to mention: I do register events during the app initialization lazily, either when the event is first published or when a subscription is created. |
Yep, but I want to raise an explicit (and descriptive) exception instead of falling back to def serialized_record_to_event(record)
event_type = events_class_remapping.fetch(record.event_type) { record.event_type }
# ...
end |
Maybe the solution here could look like: class AnyGivenMapper
def initialize(event_builder: EventTypeToClass.new) # and some other dependencies as required
@event_builder = event_builder
end
...
def serialized_record_to_event(record)
event_builder(event_type).call(
event_id: record.event_id,
metadata: TransformKeys.symbolize(serializer.load(record.metadata)),
data: serializer.load(record.data)
)
end
I will try to explore that idea in a PR :) |
Here start of the implementation #564 |
@palkan could you show us the code? Where do you call the custom methods from? How do you access the mapper object to call those methods? I assume it is possible to keep the reference to the inner mapper as a global constant and call these additional methods on it instead of on the wrapped, instrumented mapper. Any reason not to go this way? Thank you for reporting the issue :) |
Hi @paneq!
Yeah, it's possible. But as I already mentioned, Mapper is the simplest example. What about dispatchers and repositories? Do you want to make their API un-extensible as well? If so, it would be great to make it more clear for such developers as me that this entities should not have custom API methods. |
@palkan Thank you, I will need to think about your answer :) |
Current RailsEventStore version: 0.36.0
Failing RailsEventStore version: 0.37.0+
I'm using custom mapper with additional API.
Tried to upgrade the gem version and found that everything is broken)
InstrumentedMapper
only takes care of the basic mapper APIs assuming there is no additional methods. Which, I guess, true in most cases. Though it still looks too opinionated (and blocks me from upgrading 🙂).I've been thinking about a ways to solve it:
SimpleDelegator
)prepend
instrumentation functionality (viamapper.prepend
– involves singleton class creation, though not a big deal in this case)Client.new
and add a separateInstrumentedClient.new
with automatic wrapping (and add a module that could be included to custom mappers to seamlessly add instrumentation)All the applied are also applied to instrumented dispatchers and repositories.
Would be glad to provide a PR when/if we decide on a way to solve this.
Thanks)
The text was updated successfully, but these errors were encountered: