Ben*_*ter 12 c# messaging domain-driven-design ravendb domain-events
我一直在使用域事件模式 - 它使我们能够在域层中尽可能多地封装行为,并为我们的应用程序的其他部分订阅域事件提供了一种很好的方法.
目前我们正在使用一个静态类,我们的域对象可以调用它来引发事件:
static class DomainEvents
{
public static IEventDispatcher Dispatcher { get; set; }
public static void Raise<TEvent>(TEvent e)
{
if (e != null)
{
Dispatcher.Dispatch(e);
}
}
}
Run Code Online (Sandbox Code Playgroud)
正如您所看到的,这仅仅是一个IEventDispatcher
实际执行调度或发布事件的垫片.
我们的调度程序实现只是使用我们的IoC容器(StructureMap)来定位指定类型的事件的事件处理程序.
public void Dispatch<TEvent>(TEvent e)
{
foreach (var handler in container.GetAllInstances<IHandler<TEvent>>())
{
handler.Handle(e);
}
}
Run Code Online (Sandbox Code Playgroud)
这在大多数情况下都可以.但是,这种方法存在一些问题:
仅在成功持久保存实体时才应分派事件
参加以下课程:
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 });
}
}
Run Code Online (Sandbox Code Playgroud)
在Order
构造函数中,我们提出了一个OrderRaisedEvent
.在我们的应用程序层,我们可能会创建订单实例,将其添加到我们的数据库"session",然后提交/保存更改:
var order = new Order(amount: 10);
session.Store(order);
session.SaveChanges();
Run Code Online (Sandbox Code Playgroud)
这里的问题是在我们成功保存Order实体(提交事务)之前引发域事件.如果保存失败,我们仍然会调度事件.
更好的方法是将事件排队,直到实体被持久化.但是,我不确定如何在保持强类型事件处理程序的同时实现这一点.
在实体持久化之前,不应创建事件
我面临的另一个问题是在存储实体(RavenDB - session.Store
)之前不会设置/分配我们的实体标识符.这意味着在上面的示例中,实际传递给事件的订单标识符null
.
由于我不确定如何能够预先实际生成RavenDB标识符,因此一种解决方案可能是延迟事件的创建,直到实际实际保存为止,但同样我不是最好实现这一点 - 可能排队一组Func<TEntity, TEvent>
?
一个解决方案(由@synhershko建议)是将域事件的调度移到域外.这样我们就可以确保在提出任何事件之前我们的实体是持久的.
但是,我们现在将行为从域(它所属的域)移到我们的应用程序中,只是为了解决我们的持久性技术 - 我并不高兴.
如果实体成功持久化,则只应调度我的事件解决方案,即创建一个对事件进行排队的延迟事件调度程序.然后我们将调度程序注入我们的工作单元,确保我们首先持久保存我们的实体,然后发出域事件:
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
}
}
Run Code Online (Sandbox Code Playgroud)
基本上这实现了与@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);
}
Run Code Online (Sandbox Code Playgroud)
然后,我可以使用它来生成ID并将其传递给我的实体构造函数:
var orderId = session.GenerateIdFor<Order>();
var order = new Order(orderId, 1.99M);
Run Code Online (Sandbox Code Playgroud)