gpt4 book ai didi

c# - 延迟域事件的创建和调度

转载 作者:可可西里 更新时间:2023-11-01 08:24:27 25 4
gpt4 key购买 nike

我一直在使用 Domain Events pattern一段时间以来 - 它使我们能够在我们的领域层中封装尽可能多的行为,并为我们应用程序的其他部分提供一种很好的方式来订阅领域事件。

目前我们正在使用一个静态类,我们的域对象可以调用它来引发事件:

static class DomainEvents
{
public static IEventDispatcher Dispatcher { get; set; }

public static void Raise<TEvent>(TEvent e)
{
if (e != null)
{
Dispatcher.Dispatch(e);
}
}
}

如您所见,这只不过是 IEventDispatcher 的垫片。它实际上执行调度发布 事件的工作。

我们的调度程序实现仅使用我们的 IoC 容器 (StructureMap) 来定位指定事件类型的事件处理程序。

public void Dispatch<TEvent>(TEvent e)
{
foreach (var handler in container.GetAllInstances<IHandler<TEvent>>())
{
handler.Handle(e);
}
}

这在大多数 情况下都可以正常工作。但是,这种方法存在一些问题:

仅当实体成功持久化时才应分派(dispatch)事件

参加以下类(class):

public class Order
{
public string Id { get; private set; }
public decimal Amount { get; private set; }

public Order(decimal amount)
{
Amount = amount;
DomainEvents.Raise(new OrderRaisedEvent { OrderId = Id });
}
}

Order构造函数我们提出一个 OrderRaisedEvent .在我们的应用程序层中,我们可能会创建订单实例,将其添加到我们的数据库“ session ”中,然后提交/保存更改:

var order = new Order(amount: 10);
session.Store(order);

session.SaveChanges();

这里的问题是在我们成功保存订单实体(提交交易)之前引发了领域事件。如果保存失败,我们仍然会发送事件。

更好的方法是将事件排队直到实体被持久化。但是,我不确定如何在维护强类型事件处理程序的同时最好地实现它。

不应创建事件直到实体被持久化

我面临的另一个问题是我们的实体标识符在存储实体之前不会被设置/分配(RavenDB - session.Store)。这意味着在上面的示例中,传递给事件的订单标识符实际上是 null .

因为我不确定如何预先实际生成 RavenDB 标识符,一个解决方案可能是延迟事件的创建,直到实体被实际保存,但我又不知道如何最好地实现这个——也许是排队一个集合的 Func<TEntity, TEvent>

最佳答案

一种解决方案(如@synhershko 所建议的那样)是将域事件的调度移到域之外。通过这种方式,我们可以确保我们的实体在引发任何事件之前持久化。

但是,我们现在正在将行为从域(它所属的地方)移到我们的应用程序中,只是为了解决我们的持久性技术——我对此不太高兴。

我的只有在实体成功保留时才应分派(dispatch)事件的解决方案是创建一个延迟事件分派(dispatch)器来对事件进行排队。然后,我们将调度程序注入(inject)到我们的工作单元中,确保我们首先持久化/保存我们的实体,然后发出领域事件:

public class DeferredEventDispatcher : IEventDispatcher
{
private readonly IEventDispatcher inner;
private readonly ConcurrentQueue<Action> events = new ConcurrentQueue<Action>();

public DeferredEventDispatcher(IEventDispatcher inner)
{
this.inner = inner;
}

public void Dispatch<TEvent>(TEvent e)
{
events.Enqueue(() => inner.Dispatch(e));
}

public void Resolve()
{
Action dispatch;
while (events.TryDequeue(out dispatch))
{
dispatch();
}
}
}

public class UnitOfWork
{
public void Commit()
{
session.SaveChanges();
dispatcher.Resolve(); // raise events
}
}

基本上这实现了与@synhershko 所建议的相同的东西,但在我的域内保持事件的“引发”。

至于在实体被持久化之前不应创建事件,主要问题是实体标识符是由 RavenDB 在外部设置的。使我的域持久无知且易于测试的解决方案是简单地将 id 作为构造函数参数传递。如果使用 SQL 数据库(通常传递 Guid),这就是我会做的。

幸运的是,RavenDB 确实为您提供了一种使用 hilo 策略生成标识符的方法(因此我们可以保留 RESTful 标识符)。这是来自 RavenDB Contrib项目:

public static string GenerateIdFor<T>(this IAdvancedDocumentSessionOperations session)
{
// An entity instance is required to generate a key, but we only have a type.
// We might not have a public constructor, so we must use reflection.
var entity = Activator.CreateInstance(typeof(T), true);

// Generate an ID using the commands and conventions from the current session
var conventions = session.DocumentStore.Conventions;
var databaseName = session.GetDatabaseName();
var databaseCommands = session.GetDatabaseCommands();
return conventions.GenerateDocumentKey(databaseName, databaseCommands, entity);
}

然后我可以使用它来生成一个 ID 并将其传递到我的实体构造函数中:

var orderId = session.GenerateIdFor<Order>();
var order = new Order(orderId, 1.99M);

关于c# - 延迟域事件的创建和调度,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/20792907/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com