我和domain events一起研究域驱动设计。我非常喜欢这些事件提供的关注点的分离。我遇到了持久化域对象和提升域事件的问题。我想在域对象中引发事件,但我希望它们是持久性无知的。
我已使用此ShoppingCartService
方法创建了基本Checkout
:
public void Checkout(IEnumerable<ShoppingCartItem> cart, Customer customer)
{
var order = new Order(cart, customer);
_orderRepositorty.Add(order);
_unitOfWork.Commit();
}
在此示例中,Order
的构造函数将引发OrderCreated
事件,该事件可由某些处理程序处理。但是,我不希望在实体尚未持久化或持久化以某种方式失败时引发这些事件。
为了解决这个问题,我想出了几个解决方案:
1。提升服务中的事件:
我可以在服务中引发事件,而不是在域对象中引发事件。在这种情况下,Checkout
方法会引发OrderCreated
事件。这种方法的一个缺点是,通过查看Order
域对象,不清楚哪些事件是由哪些方法引发的。此外,开发人员必须记住在其他地方创建订单时提出事件。感觉不对劲。
另一种选择是对域事件进行排队,并在持久成功时引发它们。这可以通过using
语句来实现,例如:
using (DomainEvents.QueueEvents<OrderCreated>())
{
var order = new Order(cart, customer);
_orderRepositorty.Add(order);
_unitOfWork.Commit();
}
QueueEvents<T>
方法会将布尔值设置为true
,DomainEvents.Raise<T>
方法会将事件排队,而不是直接执行。在QueueEvent<T>
的dispose回调中,执行了排队事件,以确保已经发生持久化。这似乎相当棘手,它需要服务知道域对象中引发了哪个事件。在我提供的示例中,它也只支持一种类型的事件,但是,这可以解决。
我可以使用域事件来持久保存对象。这似乎没问题,除了持久化对象的事件处理程序应首先执行这一事实,但我在某处读到域事件不应该依赖于特定的执行顺序。也许这并不重要,域事件可能以某种方式知道处理程序应该执行的顺序。例如:假设我有一个定义域事件 handler 的接口,实现将如下所示:
public class NotifyCustomer : IDomainEventHandler<OrderCreated>
{
public void Handle(OrderCreated args)
{
// ...
}
}
当我想处理持久使用事件处理程序时,我会创建另一个处理程序,派生自同一个接口:
public class PersistOrder : IDomainEventHandler<OrderCreated>
{
public void Handle(OrderCreated args)
{
// ...
}
}
}
现在NotifyCustomer
行为取决于数据库中保存的顺序,因此PersistOrder
事件处理程序应首先执行。是否可以接受这些处理程序引入一个属性,例如表明它们执行的顺序?快速执行DomainEvents.Raise<OrderCreated>()
方法:
foreach (var handler in Container.ResolveAll<IDomainEventHandler<OrderCreated>>().OrderBy(h => h.Order))
{
handler.Handle(args);
}
<小时/> 现在我的问题是,我还有其他选择吗?我错过了什么吗?您如何看待我提出的解决方案?
答案 0 :(得分:6)
您的(事务性)事件处理程序在(可能是分布式的)事务中登记,或者在事务提交后发布/处理事件。你的&#34; QueueEvents&#34;解决方案获得了正确的基本想法,但有更优雅的解决方案,例如通过存储库或事件存储发布。有关示例,请查看SimpleCQRS
您可能还会发现这些问题和答案很有用:
CQRS: Storing events and publishing them - how do I do this in a safe way?
Event Aggregator Error Handling With Rollback
第3点更新:
...但我在某处读到域事件不应该依赖于特定的执行顺序。
无论你的坚持方式如何,事件的顺序绝对重要(在一个聚合中)。
现在NotifyCustomer行为取决于数据库中保存的顺序,因此应首先执行PersistOrder事件处理程序。可以接受这些处理程序引入一个属性,例如表明它们执行的顺序吗?
持久和处理事件是一个单独的问题 - 不要使用事件处理程序持续存在。首先坚持,然后处理。
答案 1 :(得分:0)
免责声明:我不知道我在说什么。 ⚠️
除了在ShoppingCartService
中引发事件之外,还可以在OrderRepository
中引发事件。
作为在ShoppingCartService
中排队域事件的一种替代方法,您可以通过从提供Order
方法的基类继承而在AddDomainEvent
聚合中将它们排队。
public class Order : Aggregate
{
public Order(IEnumerable<ShoppingCartItem> cart, Customer customer)
{
AddDomainEvent(new OrderCreated(cart, customer))
}
}
public abstract class Aggregate
{
private List<IDomainEvent> _domainEvents = new List<IDomainEvent>();
public IReadOnlyCollection<IDomainEvent> DomainEvents => _domainEvents.AsReadOnly();
public void AddDomainEvent(IDomainEvent domainEvent)
{
_domainEvents.Add(domainEvent);
}
}