gautema / CQRSlite

A lightweight framework to help creating CQRS and Eventsourcing applications in C#
Other
1.1k stars 266 forks source link

Support for non-Guid Identity #28

Closed petersondrew closed 5 years ago

petersondrew commented 7 years ago

Continuing my saga of implementing EventStore as an IEventStore implementation, I've found that limiting identity to Guid limits the flexibility in how we store and categorize our aggregates/events, with categorization being the primary driver personally.

I envision being able to use strings, such as Order-<someGuid>, or Order-1, which is especially important for event stores that use an identity prefix to categorize events and aggregates (such as EventStore).

Any thoughts or opinions on implementing something like this? It could be as simple as replacing Guid with string, or introducing some sort of IIdentity to use. I'd be more than happy to submit a PR for this change, but wanted to know what your thoughts were.

gautema commented 7 years ago

I have thought about this before, but dropped it because I felt it made things to complex. I don't like the idea of using strings that well and haven't figured out a good solution using generics or something like iidentity without making it unnecessary complex for most users. I'm open to suggestions...

On 6 Jun 2017, at 23:36, Drew Peterson notifications@github.com wrote:

Continuing my saga of implementing EventStore as an IEventStore implementation, I've found that limiting identity to Guid limits the flexibility in how we store and categorize our aggregates/events, with categorization being the primary driver personally.

I envision being able to use strings, such as Order-, or Order-1, which is especially important for event stores that use an identity prefix to categorize events and aggregates (such as EventStore).

Any thoughts or opinions on implementing something like this? It could be as simple as replacing Guid with string, or introducing some sort of IIdentity to use. I'd be more than happy to submit a PR for this change, but wanted to know what your thoughts were.

— You are receiving this because you are subscribed to this thread. Reply to this email directly, view it on GitHub, or mute the thread.

petersondrew commented 7 years ago

I just finally got around to finishing up a proof-of-concept implementing an IIdentity. I included an out-of-the-box GuidIdentity implementation. Those of us who need something more advanced can easily do so.

andymacdonaldac commented 7 years ago

We faced a similar issue, internally we name our streams typename-aggregateid so for an Availability aggregate the stream would be named something like availability-d0057d1d-b431-42f3-b8ff-bc536a9ea286.

The first thing we did was to add an ITypedEventStore interface which replicated the IEventStore interface but adds an aggregateTypeName parameter to the start of the Get and Save methods:

public interface ITypedEventStore
{
    Task<IEnumerable<IEvent>> Get(string aggretateTypeName, Guid aggregateId, int fromVersion, CancellationToken cancellationToken = default(CancellationToken));
    Task Save(string aggretateTypeName, IEnumerable<IEvent> events, CancellationToken cancellationToken = default(CancellationToken));
}

We created a new TypedRepository which implements IRepository and takes an ITypedEventStore in it's constructor.

private readonly ITypedEventStore _eventStore;

public TypedRepository(ITypedEventStore eventStore)
{
    _eventStore = eventStore ?? throw new ArgumentNullException(nameof(eventStore));
}

Then using reflection we pulled out the type name of the aggregate inside the Save and LoadAggregate methods e.g.

public async Task Save<T>(T aggregate, int? expectedVersion = null, CancellationToken cancellationToken = default(CancellationToken)) where T : AggregateRoot
{
 string typeName = aggregate.GetType().Name.ToLower();
}

and

private async Task<T> LoadAggregate<T>(Guid id, CancellationToken cancellationToken = default(CancellationToken)) where T : AggregateRoot
{
string typeName = typeof(T).Name.ToLower();
}

That type name ultimately gets passed to the ITypedEventStore implementation which is this case pushes to GetEventStore.

It's still very much a work in progress and some of the type names can probably be changed to something better but it works for us at the moment. I've attached the files we used and if anyone think's it worthwhile I'm happy to take a fork, make the changes and open a proper PR.

Code.zip

Andy

bdongus commented 6 years ago

In the beginnig I too had problems with the id being a Guid. After some meditation I wouldn't introduce a non-Guid id. Those ids are IMHO a dependency to some sort of storage and legacy to data driven thinking. Autogenerated integer ids. As soon as you have a distributed storage scenario you are screwed using them. I use the autogenerated integer id as clustering index and the the Guid as unique index. That way you prevent the disk acitivity caused by the Guid as clustering index.