modernice/goes

Event Metadata

Opened this issue · 4 comments

Objective

To extend the event store's capabilities by allowing the storage of additional metadata for events and provide the necessary querying mechanisms to filter based on various metadata types.

Requirements

  1. Metadata Storage: Ability to associate and store metadata with events in the event store.
  2. Advanced Querying: Support filtering based on various metadata types, including:
    • Strings
    • Integers
    • Booleans
    • Arrays

Example Usage

1. Creating an Event with Metadata:

func createEventWithMetadata() {
  evt := event.New("<eventName>", "<eventData>",  event.Metadata(map[string]any{
    "field-a": "value-a",
    "field-b": []string{"value-b", "value-c"},
  }))

  evt.Metadata()["field-a"] == "value-a"
  evt.Metadata()["field-b"] == []string{"value-b", "value-c"}
}

2. Querying Events Based on Metadata:

func queryEventsByMetadata(store event.Store) {
  // Query by exact metadata value.
  events, errs, err := store.Query(context.TODO(), query.New(
    query.Metadata("field-a", "value-a"),
  ))

  // Query by exact metadata array values (element order matters).
  events, errs, err := store.Query(context.TODO(), query.New(
    query.Metadata("field-b", []string{"value-b", "value-c"}),
  ))

  // Query based on the presence of specific values within an array.
  // Either "value-a" OR "value-c" must be present.
  events, errs, err := store.Query(context.TODO(), query.New(
    query.Metadata("field-b", metadata.Contains("value-a", "value-c")),
  ))

  // Query based on the presence of specific values within an array.
  // Both "value-a" AND "value-c" must be present.
  events, errs, err := store.Query(context.TODO(), query.New(
    query.Metadata("field-b", metadata.ContainsAll("value-a", "value-c")),
  ))
}

Maybe just add additional filters for querying event data.

Query by field

This can only work if the event data can be accessed by field (e.g. JSON). Event stores may or may not support this.

func queryEventsByData(store event.Store) {
  store.Query(context.TODO(), query.New(
    query.AggregateName("user"),
    query.Field("age", 28, 32), // either 28 or 32 years old
  ))

  store.Query(context.TODO(), query.New(
    query.AggregateName("user"),
    query.Field("age", field.Gt(30), field.Lt(40)), // greater than 30, less than 40 years
  ))

  store.Query(context.TODO(), query.New(
    query.AggregateName("user"),
    query.Field("name", "Bob", "Linda"),
  ))

  store.Query(context.TODO(), query.New(
    query.AggregateName("user"),
    query.Field("name", field.Contains("ob")), // will find "Bob"
  ))

  store.Query(context.TODO(), query.New(
    query.AggregateName("user"),
    query.Field("skills", []string{"programming", "walking"}),
  ))

  store.Query(context.TODO(), query.New(
    query.AggregateName("user"),
    query.Field("skills", field.Contains("programming", "walking")),
  ))

  store.Query(context.TODO(), query.New(
    query.AggregateName("user"),
    query.Field("skills", field.ContainsAll("programming", "walking")),
  ))
}

Also interested in this and would like to help if you've got any ideas on how this may fit into things. I think the metadata approach, while less generic, will likely be easier to support across all event stores in some way or another. My use case is being able to simply query by tenant in a multi-tenant application, which is easily solved with metadata.

Hi @plunkettscott, thank you for your interest in contributing to the project!

I agree with you that the metadata approach seems to be more portable implementation-wise, as each event store can decide for itself on how to store the metadata, and does not rely on the user-defined structure of the event data. My thought was that event metadata queries would likely not be much more complex to implement than queries for the entire event data, but after thinking a bit more about this, separating metadata from the main event data will probably be less confusing to users because they don't have to check for "supported features" when choosing an event store backend.

Regarding multi-tenant support, it's interesting that you brought this up as multiple people have been reaching out with similar inquiries. As you said, supporting this should be pretty easy using the metadata approach. Unfortunately, I hadn't had the time to work on this feature yet and will likely not be able to invest much time into this project until at least January.

That being said, I think the implementation of this feature should be pretty straight-forward, so if you're interested in implementing this, I would be happy to merge. Otherwise I will take a deeper look into this in the upcoming weeks.

Yeah, I’ll take a look at this closer and see if I have some time to help out.

Another nice feature for multi-tenancy would be multi-tenancy support in the authentication package, but I’ll create a separate issue for that.