gautema/CQRSlite

Would it make sense to have generic IEventStore implementation

feugen24 opened this issue · 5 comments

The problem is that I want to add metadata to events, for example correlationId, issuer. I could derive from
IEvent but I would need to create wrapper for IEventStore and maybe casts in other places.

So for example instead of:
Task<IEnumerable<IEvent>> Get(Guid aggregateId, int fromVersion, CancellationToken cancellationToken = default(CancellationToken));

Could it be:

Task<IEnumerable<T>> Get(Guid aggregateId, int fromVersion, CancellationToken cancellationToken = default(CancellationToken)) where T:IEvent;

also in the IEvent interface there is:

Guid Id { get; set; }

but in the AggregateRoot , FlushUncommitedChanges there is

if (@event.Id == Guid.Empty)
 {
    @event.Id = Id;
 }

so more events have Id of the aggregate. So the Event.Id filed is more like Event.AggregateId.

'Id' would imply a unique identifier for events not a reference to the AggregateId

Hi.
I think making the eventstore generic would make the api more complicated and harder to use for most users. I've had a lot of people wanting more things to be generic throughout the years. Right now there are two open issues with people wanting more things to be generic. I try to keep things as easy as possible, because I think it's better to start with this framework and grow out of it, than to never start with it at all. If you need more fields on an event, I think a better approach would be to fork the project or copy the files in to your own code and change it there.

As for the second comment, I think you might be right that it would be better to call it aggregateId, however the api is a bit hard to change now, since a lot of people are using it and changing it would make it very hard to upgrade to a new version.

@gautema I get your points, I considered forking but I wanted your thoughts first. I suppose I'll go with forking and try to merge with changes from this repository later on.

I'm working for a kind of proof of concept using event sourcing for one of our clients. In this context there will probably be other developers using the code and since ES/CQRS is not that mainstream each new small thing adds up to complexity/adoption in the sense that probably a developer could mentally translate Id to AggregateId but I try to reduce that effort as much as possible.

As a suggestion I tried learning from the 2 article links in the home project screen but the Greg Young presentation was far more useful for me and it's closely related to how the source code looks, so you could consider linking it as a learning source.

I'll take a look at the presentation. And I'll consider renaming event id again.

I am having the same issue now. It works brilliant with your InMemory Dictionary.. but I have not really found any body with a good example using this and the EventStore.org DB.. every body use the in memory one for their examples.

The problem is EventStore.org DB (noted as ES from here on to not confuse in memory cache you have) requires a stream name... Now I have struggling two days to try and get it working as your Framework from NuGet. But I had to buckle in today, download it and import the project into mine and make some change.

As great and simple this project is.. There is very little room to extend it ;( I have been trying to work around everything and managed in most part, as @feugen24 mentioned with some casting (covariance) on IEVent in the save on ES which works OK but for Get there is no interface element so I cannot rebuild the stream name based on shit I put in a covariant interface.

Task Save(IEnumerable<IEvent> events, CancellationToken cancellationToken = default(CancellationToken));

I made an interface IEventWithStreamName and casted the IEvent to that when saving to ES - It is not amazing.. and a bit confusing but it works.

Task<IEnumerable<IEvent>> Get(Guid aggregateId, int fromVersion, CancellationToken cancellationToken = default(CancellationToken));

How do I regenerate the stream name from this?

And yes, I even went to saving each aggravate on its own stream "aggregateId.ToString()" which would have worked.. but then...


... Is it expected the Aggregate ID changes when events are applied? So this broke it all now because each save will be saving to a different stream.

LoadFromHistory in AggregateRoot

 ApplyEvent(e);
 Id = e.Id;
 Version++;

You apply the event, then set the AggregateId to the EventId?? (which in my case I am using ES and each event must have a different ID)

Will probably make some small changes and will have to maintain our own package.

Thanks for a great framework any way. Allot of people use it..

Just for fun, try and get eventStore.org ES to work with your framework and add the example this git.. I wonder if you will run into the same problems as us?

Edit
Just for any body else looking into EventStore we changed it to be ES stream changes rather than in memory versions

While working a few days with the framework we decided to gut out a few things, like version and timestamp on the IEvent

  • add a dynamic Metadata (with JsonIgnoreAttribute) can save things like IP, UserName, AssemblyQualifiedName, etc
  • Removed TimeStamp from IEvent - The time stamp is handled automatically in EventStore and we rather it handle this (other wise we had it duplicated and out of sync) We assign it back to the Metadata dynamic on get
  • as the dynamic metadata is reconstructed on load we can also access the EventNumber provided by the EvenstoreAPI - Which is like the Version we just ripped out, so if we were really worried about concurrency we could use THIS but in our case it will be highly unlikely for concurrency issues to occur so we just don't check. You may need it though, use EventNumber.
  • we added a stream name into the IEventStore so we can save aggregates like Customer+00000-000-000-000-0000 and also renamed fromVersion to fromEventNumber
    • Task Save(string streamName, IEnumerable<IEvent> events, CancellationToken cancellationToken = default(CancellationToken));
    • Task<IEnumerable<IEvent>> Get(string streamName, Guid aggregateId, int fromEventNumber, CancellationToken cancellationToken = default(CancellationToken));