-
-
Notifications
You must be signed in to change notification settings - Fork 19
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
Reshaped MongoDBEventStore initialisation #136
Conversation
Reshaped MongoDBEventStore initialisation to allow passing client or connection string. That should make it more accessible. Internally it'll ensure that client is connected, and allow closing client through exposed close method if it was created by the event store. Hidden also class and exposed type to allow replacing MongoDBEventStore by inMemory implementation for integration tests. Passing client will enable more advanced features further on like sessions, transactions, etc. Having collection per stream type will also make easier indexing of inline projections and other optimisations.
@alex-laycalvert, could you have a look? 🙏 🙂 |
In the longer term, we want to store also commands sometimes, not only events. This change should stabilise the payload.
f4ee088
to
455e3ec
Compare
02f99c9
to
2f6d2eb
Compare
@@ -246,13 +284,13 @@ export class MongoDBEventStore implements EventStore<MongoDBReadEventMetadata> { | |||
readModels: stream?.projections ?? {}, | |||
events: eventsToAppend, | |||
projections: this.inlineProjections, | |||
collection: this.collection, | |||
collection, | |||
updates, | |||
client: {}, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I didn't see this on the updates to projections, but for inline projections a client isn't necessary right? Or is it just because the context for inline projections and async projections (once implemented) will be the same type?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, that's true: for now. If we have a strategy to have a collection per stream type, then technically, this could be placed in a separate Mongo database. For that, we'd need the client to resolve the collection. Having collection in different databases would help to have e.g. separation of data storage per module or per tenant and still use a single event store singleton setup.
private readonly client: MongoClient; | ||
private readonly defaultOptions: { | ||
database: string | undefined; | ||
collection: string | undefined; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we should add some explicit documentation that not providing a collection
would have each stream type separated into a separate coll., but providing a collection
would only use that for everything. Just to avoid it not being obvious that this changes how the streams are stored.
Maybe we also allow for a collectionFor
option to allow the dev the option to deal with this.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we should add some explicit documentation that not providing a collection would have each stream type separated into a separate coll., but providing a collection would only use that for everything. Just to avoid it not being obvious that this changes how the streams are stored.
Yes, that's a good call, what would you say if we force user to make an explicit choice through the option and have event store options that'd have a field in event store option { storage: 'SINGLE_COLLECTION' }
or { storage: 'COLLECTION_PER_STREAM_TYPE' }
, that'd allow to discriminate user choice and force to e.g. provide collection for the first option, and client for the second (and having that mutually exclusive).
Maybe we also allow for a collectionFor option to allow the dev the option to deal with this.
Just to double-check: would you give user an option to provide their own resolution function through store options? I like the idea!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah choosing single/multi collection storage I think is the way to go. I think something like this:
type Options =
| { type: 'SINGLE_COLLECTION', collection?: string }
| {
type: 'COLLECTION_PER_STREAM_TYPE',
collectionFor?: (streamType: string) => Promise<Collection> | Collection
}
I think it can be the same type def as the private collectionFor
. Could allow for grouping certain stream types together into the same collection, not that it's necessarily useful.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I added issue for that: #142
if (this.isClosed) return Promise.resolve(); | ||
|
||
this.isClosed = true; | ||
if (!this.shouldManageClientLifetime) return Promise.resolve(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Probably should add some docs either on close
or on the client
option to make sure the user knows that we only attempt to close the client
if we are managing it. Looking at the code I can see this and understand why but I can imagine a scenario where a user attempts to close the event store but they had passed a client
, and even though this.isClosed
is true, the client is still open and calling this function has no effect on it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We might also want to add some checks for isClosed
in collectionFor
or the individual functions for interacting with the event store. I think if the client is actually closed it'll throw an error when you try to use it and it's been closed, but in the above example, you could technically call close on the eventstore even if you passed your own client and it essentially does nothing.
I might be missing something obvious in why this was intended, just my thoughts.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Probably should add some docs either on close or on the client option to make sure the user knows that we only attempt to close the client if we are managing it.
Yes, makes sense. Besides docs, I could also make some typescript Voodoo with type interference and expose the close
method only if the user passed the client through options (so essentially, we'd have different types for managed event store and "ambient" one). But I'm not sure if it'd be more intuitive or not. Currently calling close is a safe default for both options, as it shouldn't cause any side-effects. Thoughts? 🤔
We might also want to add some checks for isClosed in collectionFor or the individual functions for interacting with the event store.
I was thinking on it, but then if someone closed the event store then it'll get the same MongoDB errors as they'd get from a regular usage. But it may be worth indeed wrapping it with some helper and more user-friendly Emmett errors. Thoughts? 🤔
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah that makes sense, I think that's fine. Some TS magic to not expose close
if client is provided would probably be enough.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added issue for that: #143
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we might just want to add some end user docs before things get too fancy. It might just be me but there's definitely some functionality in here that's not entirely obvious from what this module exports that could cause unexpected behavior.
@alex-laycalvert thank you a lot for a honest review, that's much appreciated! |
c02d61d
to
874a976
Compare
874a976
to
50b31e7
Compare
@alex-laycalvert, per our agreement on Discord, I've set up issues for the follow-up typing improvements. As they're not blocking, let's get this PR in, and I'll work in the next few days to fill the missing gap. |
1. Reshaped MongoDBEventStore initialisation to allow passing client or connection string.
That should make it more accessible.
Internally, it'll ensure that the client is connected and allow closing the client through the exposed close method if it was created by the event store.
Also hidden is the class and exposed type to replace MongoDBEventStore with in-memory implementation for integration tests.
Passing clients will enable more advanced features further on, like sessions, transactions, etc.
Having collection per stream type will also make easier indexing of inline projections and other optimisations.
2. Renamed events to messages
In the longer term, we want to store also commands sometimes, not only events. This change should stabilise the payload.