Skip to content
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

Allow to define source of default streams when publishing events #1739

Open
swistak35 opened this issue Feb 9, 2024 · 3 comments
Open

Allow to define source of default streams when publishing events #1739

swistak35 opened this issue Feb 9, 2024 · 3 comments

Comments

@swistak35
Copy link
Contributor

swistak35 commented Feb 9, 2024

I know that more-than-one project are not using publishing though event store directly:

event_store.publish(OrderPublished, stream_name: "Order$#{order.id}")

Because of requirement to always pass stream name, which seems very reasonable when you read the docs, but in practice seem very tedious, since you end up with copy-pasting stream name everywhere, so people end up with some facades over event store to relieve that, so that you can publish your events without specifying stream name each time, and instead "fetch it" from different place. This is less of a problem in greenfield, event-sourcing or very neatly bounded systems, but you pretty much have to face that issue in legacy environments.

Examples I've saw:

  1. event_store.publish(OrderPublished.new(...)). event_store here is a wrapper over RES, which inside has a mapping like OrderPublished => ->(event) { "Order$#{event.data.fetch(:order_id)}" }
  2. Event.publish!(OrderPublished.new(...), order), which inside have a mapping from an object (order) to the stream to which this event is relevant.

I think the problem is common enough, that it would make sense to provide ability to set up some kind of StreamsNameProvider (name is WIP), which would be able to answer the questions about the streams for given event.

We would preserve current behaviour by allowing to override these defaults by the stream_name: keyword argument, as it is now.

An example implementation of such provider within a project:

module EventStore
  class StreamsNameProvider
    MAPPING = {
      "OrderPublished" => ->(event) { ["Order$#{event.data.fetch(:order_id)}"] }
    }
    def call(event, *, **)
      MAPPING.fetch(event.event_type) || []
    end
  end
end

This seem flexible enough to cover most of production cases, and should be relatively easy for extension. To be clear, I am willing to implement it, but I'm opening a discussion.

WDYT?

@mostlyobvious
Copy link
Member

mostlyobvious commented Feb 27, 2024

Does it assume you always append one event, as in the examples? If it allows many events, does it group them by streams and executes multiple appends — one per stream?

My gut feeling is — this is fine for a framework on top of RES. And then perhaps a definition of go-to stream should be close to the definition of the event.

@swistak35
Copy link
Contributor Author

Does it assume you always append one event, as in the examples? If it allows many events, does it group them by streams and executes multiple appends — one per stream?

I was thinking that there's no need to consider multiple events when doing StreamsNameProvider#call, but it can be interesting idea to put the grouping logic (which I can imagine, there can be more than one), into StreamsNameProvider default implementation. So actually, idd, I'd change the API to #call(events)

My gut feeling is — this is fine for a framework on top of RES.

Where is it then (that framework)? I see ourselves blocking from implementing multiple slightly opinionated aspects of event-driven library (versioning, data structure validation, this issue) because it won't fit the RES-core (so to speak), and in the meantime these will likely get reimplemented in each project. And being able to specify (any implementation of) StreamsNameProvider isn't even that opinionated 🙃

I don't see a problem in reimplementing per se (we don't owe anyone particular features), but with that we're blocking ourselves from implementing smarter RES-Browser. It won't be easy to implement some, for example, versioning functionality on the browser without at least providing some commonly agreed API on it (like "What was the previous version of that event?").

And then perhaps a definition of go-to stream should be close to the definition of the event.

That I agree, the example was just to be simple example, but #call can do anything having event object at hand

@swistak35
Copy link
Contributor Author

As an additional highlight, if you're worried about backwards compatibility, we may mark this as experimental feature for some time: https://railseventstore.org/docs/v2/maintenance_policy/#experimental-features

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants