lundi 20 août 2018

Serialize and Deserialize domain events to persist and retrieve from Event Store in generic implementation

I am using DDD with CQRS and Event Sourcing. I need to use an Event Store (specifically this event store) within my custom implementation of IEventStore to persist and retrieve domain events but I am having difficulties with the approach to take that deals with serialization/deserialization.

This is the interface I am implementing:

public interface IEventStore
{
    Task<IEnumerable<IDomainEvent>> GetEventsAsync(Identity aggregateIdentity, Type aggregateType);

    Task PersistAsync(IAggregateRoot aggregateRoot, IEnumerable<IDomainEvent> domainEvents);
}

Outside my implementation of IEventStore I can have mappers from every IDomainEvent into some serializable/deserializable EventDto or json string. That's not a problem. But these are my restrictions:

  • my domain events are immutable objects that implement IDomainEvent (i.e: no setters)

  • my domain events are not always easily serializable/deserializable in a generic way. Often they have abstract or interface properties, so the concrete mappers between my domain events and some serializable object such as string json or event DTO are decided outside my IEventStore implementation.

  • My IEventStore implementation needs to be generic in a way that if I add new domain event types, I should not need to touch anything within the IEventStore implementation

  • My IEventStore implementation can receive injected some specific implementations of IMapper<TSource, TDestination>, so that I could use a them to serialize/deserialize between specific types (not interfaces).

    public interface IMapper<in TSource, out TDestination>
    {
        TDestination Map(TSource source); // I have implementations of this if needed
    }
    
    

This below is my attempt:

public class MyEventStore
    : IEventStore
{
    private readonly IStreamNameFactory _streamNameFactory;
    private readonly IEventStoreConnection _eventStoreConnection; //this is the Greg Young's EventStore product that I want to use as database
    private readonly IDomainEventFactory _domainEventFactory;
    private readonly IEventDataFactory _eventDataFactory;

    public EventStore(
        IStreamNameFactory streamNameFactory, 
        IEventStoreConnection eventStoreConnection, 
        IDomainEventFactory domainEventFactory, 
        IEventDataFactory eventDataFactory)
    {
        _streamNameFactory = streamNameFactory;
        _eventStoreConnection = eventStoreConnection;
        _domainEventFactory = domainEventFactory;
        _eventDataFactory = eventDataFactory;
    }

    public async Task<IEnumerable<IDomainEvent>> GetEventsAsync(
        Identity aggregateIdentity, 
        Type aggregateType)
    {
        var aggregateIdentityValue = aggregateIdentity.Value;
        var streamName = _streamNameFactory.Create(aggregateIdentityValue, aggregateType);

        var streamEventSlice =
            await _eventStoreConnection.ReadStreamEventsForwardAsync(streamName, 0, Int32.MaxValue, false);

        var domainEvents = streamEventSlice
            .Events
            .Select(x => _domainEventFactory.Create(x));

        return domainEvents;
    }

    [SuppressMessage("ReSharper", "PossibleMultipleEnumeration")]
    public async Task PersistAsync(
        IAggregateRoot aggregateRoot, 
        IEnumerable<IDomainEvent> domainEvents)
    {
        var numberOfEvents = domainEvents.Count();
        var aggregateRootVersion = aggregateRoot.Version;
        var originalVersion = aggregateRootVersion - numberOfEvents;
        var expectedVersion = originalVersion - 1;

        var aggregateIdentityValue = aggregateRoot.AggregateIdentity.Value;
        var aggregateRootType = aggregateRoot.GetType();
        var streamName = _streamNameFactory.Create(aggregateIdentityValue, aggregateRootType);
        var assemblyQualifiedName = aggregateRootType.AssemblyQualifiedName;

        var eventsToStore = domainEvents.Select(x => _eventDataFactory.Create(x, assemblyQualifiedName));

        await _eventStoreConnection.AppendToStreamAsync(streamName, expectedVersion, eventsToStore);
    }
}

The problems is mainly, as you can imagine, in the IDomainEventFactory implementation. I need a class that implements the following interface:

public interface IDomainEventFactory
{
    IDomainEvent Create(ResolvedEvent resolvedEvent);
}

This class needs to know which specific IDomainEvent does it need to deserialize the resolvedEvent to at runtime. In other words, if the event being retrieved is a json representation of MyThingCreatedEvent maybe I can use a service such as IMapper<ResolvedEvent, MyThingCreatedEvent>. But if the event being retrieved is a json representation of MyThingUpdatedEvent then I would need a service such as IMapper<ResolvedEvent, MyThingUpdatedEvent>.

Some approaches came to my mind.

OPTION 1: I thought I could have the IDomainEventFactory implementation use the autofac IComponentContext so that at runtime I could somehow manage to do some _componentContext.Resolve(theNeededType). But I don't know how to retrieve the IMapper that I need. Maybe this is something possible but I doubt it.

OPTION 2: Maybe I could have some mapping service such as IBetterMapper such as

public interface IBetterMapping
{
    TDestination Map<TDestination>(object source) where TDestination : class;
}

so that my factory can delegate the concern of knowing how to deserialize anything into TDestination. But I would have the same problem: I don't know how to create a type at runtime from a string, for example, to do something like _myBetterMapper.Map<WhichTypeHere> and there is the additional problem of implementing that Map method, which I guess would require some registration table and based on the type choose one or another specific mapper.

I am really stuck with this. Hopefully I get some help from you guys! :)





Aucun commentaire:

Enregistrer un commentaire