序列化和反序列化域事件以在泛型实现中持久化并从事件存储中检索



我将DDD与CQRS和事件来源一起使用。我需要在IEventStore的自定义实现中使用事件存储(特别是这个事件存储)来持久化和检索域事件,但我在处理序列化/反序列化的方法方面遇到了困难。

这就是我正在实现的接口:

public interface IEventStore
{
Task<IEnumerable<IDomainEvent>> GetEventsAsync(Identity aggregateIdentity, Type aggregateType);
Task PersistAsync(IAggregateRoot aggregateRoot, IEnumerable<IDomainEvent> domainEvents);
}

在我的IEventStore实现之外,我可以将每个IDomainEvent映射到一些可序列化/可反序列化的EventDto或json字符串。这不是问题。但这些是我的限制:

  • 我的域事件是实现IDomainEvent(即:无setter)的不可变对象

  • 我的域事件并不总是以通用的方式易于序列化/反序列化它们通常具有抽象或接口属性,因此我的域事件和一些可序列化对象(如字符串json或事件DTO)之间的具体映射器是在我的IEventStore实现之外决定的。

  • 我的IEventStore实现需要是通用的,这样如果我添加新的域事件类型,我就不需要触摸IEventStore实现中的任何东西

  • 我的IEventStore实现可以接收注入的IMapper<TSource, TDestination>的一些特定实现,这样我就可以使用它们在特定类型(而不是接口)之间进行序列化/反序列化。

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

下面是我的尝试:

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);
}
}

正如您所能想象的,问题主要出现在IDomainEventFactory实现中。我需要一个实现以下接口的类:

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

此类需要知道在运行时需要将resolvedEvent反序列化到哪个特定的IDomainEvent。换句话说,如果正在检索的事件是MyThingCreatedEvent的json表示,那么我可以使用诸如IMapper<ResolvedEvent, MyThingCreatedEvent>之类的服务。但是,如果正在检索的事件是MyThingUpdatedEvent的json表示,那么我将需要像IMapper<ResolvedEvent, MyThingUpdatedEvent>这样的服务。

我想到了一些方法。

选项1:我想我可以让IDomainEventFactory实现使用autofacIComponentContext,这样在运行时我就可以设法执行一些_componentContext.Resolve(theNeededType)。但我不知道如何取回我需要的IMapper。也许这是可能的,但我对此表示怀疑。

选项2:也许我可以有一些映射服务,比如IBetterMapper,比如

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

这样我的工厂就可以将知道如何将任何东西反序列化为CCD_ 19的问题委托给我。但我也会遇到同样的问题:例如,我不知道如何在运行时从字符串创建类型,以执行类似_myBetterMapper.Map<WhichTypeHere>的操作,而且还有实现Map方法的额外问题,我想这需要一些注册表,并根据类型选择一个或另一个特定的映射器。

我真的很纠结。希望我能得到你们的帮助!:)

更新:我已经实现了自己的解决方案,并在我的个人回购中上传了该项目:https://gitlab.com/iberodev/DiDrDe.EventStore.Infra.EventStore我采用的解决方案是保持事件存储包装器的不可知性,但在DI注册时为那些有点"特殊"的事件提供自定义序列化程序/反序列化程序。EventStore允许添加自定义元数据头,因此我使用一些自定义头在每个数据流上指定具体的实现类型,以便在检索持久化事件时知道在哪里进行反序列化。

更新的答案:

随着时间的推移,我逐渐意识到整个方法是一种糟糕的做法。我认为域事件永远不应该具有抽象(多态)属性,这些属性可能具有不同的形状,因此在反序列化以确切地知道事件被序列化为什么形状时会出现问题。

这个问题不是技术性的(尽管我下面的回答仍然有效),而是哲学性的。

我坚信域事件应该只使用基本类型。不变的东西(string、int,也许还有一些"安全"的自定义类型,比如money等)。拥有多态域事件没有多大意义如果一个事件可以采取不同的形式,那么我们谈论的可能是不同的事件

重要的是要考虑到,在创建投影时(例如:在重播期间,或者只是在使用事件源实例化聚合期间)也必须反序列化一个非常旧的事件(例如:一年前引发的事件),因此应该正确地反序列化此事件而不会失败。想象一下,如果有人出于任何原因修改了事件正在使用的某个类,而现在旧的信息无法反序列化到新的类中,会出现什么混乱。我们将违反事件来源中最基本的东西。

这就是为什么我认为我们不应该对复杂对象使用域事件,除非我们100%确信这些类不会更改,并且我们根本不应该使用多态域事件。


我已经在EventStore.NET客户端上实现了一个包装器,该包装器实现了我的IEventStore接口,并从幕后提取了我的客户端应用程序。

public interface IEventStore
{
Task<IEnumerable<IDomainEvent>> GetEventsAsync(Guid aggregateId, Type aggregateType);
Task PersistAsync(IAggregateRoot aggregateRoot, IEnumerable<IDomainEvent> domainEvents);
}

我解决序列化/反序列化主要问题的方法是为"特殊"的域事件提供自定义序列化程序/反序列化程序(因为它们具有抽象或接口属性,除非知道其特定的具体类型,否则无法反序列化)。此外,对于每个持久化的域事件,我保存元数据头,说明它是哪种特定的域事件类型以及它是哪一种特定的可序列化事件类型

换句话说,当持续时,流程是这样的:IDomainEvent -> convert to a serializable type (if needed) -> transform in bytes -> save stream data

以及在检索时Stream Data -> transform to serializable type -> transform to IDomainEvent

我已将整个项目上传到GitLab的个人存储库此处:https://gitlab.com/iberodev/DiDrDe.EventStore.Infra.EventStore,请随时查看并运行xUnit的所有集成和单元测试以了解它。当然,也可以随时提供任何反馈!

我的解决方案的重担在于客户端需要使用事件存储的部分。其基础设施层(在其主机应用程序中注册Autofac)负责使用Autofac扩展注册EventStore,并在需要时提供所需的自定义序列化程序/反序列化程序。

这样,我就可以使EventStore包装器的实现完全不受特定设置和特定域事件的影响。这是一个通用的解决方案。

项目的README澄清了这一点,但基本上,如果域事件是可序列化的(没有抽象属性),则事件存储可以像这样注册:

var builder = new ContainerBuilder(); // Autofac container
builder
.RegisterEventStore(
ctx =>
{
var eventStoreOptions =
new EventStoreOptions
{
ConnectionString = "ConnectTo=tcp://admin:changeit@127.0.0.1:1113; HeartBeatTimeout=500";
};
return eventStoreOptions;
});
var container = builder.Build();

像这样,如果有域事件是特殊的,因为它们具有抽象属性:

var builder = new ContainerBuilder();
builder
.RegisterEventStore(
ctx =>
{
var eventStoreOptions =
new EventStoreOptions
{
ConnectionString = "ConnectTo=tcp://admin:changeit@127.0.0.1:1113; HeartBeatTimeout=500";
};
return eventStoreOptions;
},
ctx =>
{
var customDomainEventMappersOptions =
new CustomDomainEventMappersOptions()
.UsesCustomMappers<FakeDomainEventNotSerializable, FakeSerializableEvent>(
domainEvent =>
{
var mapper =
new FakeDomainEventNotSerializableToFakeSerializableEventMapper();
var result = mapper.Map(domainEvent);
return result;
},
serializableEvent =>
{
var mapper =
new FakeSerializableEventToFakeDomainEventNotSerializableMapper();
var result = mapper.Map(serializableEvent);
return result;
});
return customDomainEventMappersOptions;
});
var container = builder.Build();

相关内容

  • 没有找到相关文章

最新更新