如何从域对象中调度域事件?
How are domain events dispatched from within domain objects?
域对象不应该有任何依赖关系,因此也没有依赖注入。然而,当从域对象中调度域事件时,我可能想要使用集中式 EventDispatcher
。我怎样才能得到一个?
我不想 return 向调用者发送事件列表,因为我希望它们保持不透明并保证它们的调度。这些事件只能由需要强制执行最终一致约束的其他域对象和服务使用。
你要求两者兼得。您要么需要注入依赖项,要么需要反转控制并让另一个对象管理器负责 Aggregate 和 EventDispatcher 之间的交互。我建议让你的聚合尽可能简单,这样它们就没有依赖性并且保持可测试性。
以下代码示例非常简单,不会成为您投入生产的代码示例,但它说明了如何设计无依赖性的聚合,而无需在需要它们的上下文之外传递事件列表。
如果您的聚合中包含事件列表:
class MyAggregate
{
private List<IEvent> events = new List<IEvent>();
// ... Constructor and event sourcing?
public IEnumerable<IEvent> Events => events;
public string Name { get; private set; }
public void ChangeName(string name)
{
if (Name != name)
{
events.Add(new NameChanged(name);
}
}
}
然后你可能有一个看起来像这样的处理程序:
public class MyHandler
{
private Repository repository;
// ... Constructor and dependency injection
public void Handle(object id, ChangeName cmd)
{
var agg = repository.Load(id);
agg.ChangeName(cmd.Name);
repository.Save(agg);
}
}
存储库如下所示:
class Repository
{
private EventDispatcher dispatcher;
// ... Constructor and dependency injection
public void Save(MyAggregate agg)
{
foreach (var e in agg.Events)
{
dispatcher.Dispatch(e);
}
}
}
基本上,您为域事件注册一个或多个处理程序,然后引发这样的事件:
public class Customer
{
public void DoSomething()
{
DomainEvents.Raise(new CustomerBecamePreferred() { Customer = this });
}
}
并且将执行所有已注册的处理程序:
public void DoSomethingShouldMakeCustomerPreferred()
{
var c = new Customer();
Customer preferred = null;
DomainEvents.Register<CustomerBecamePreferred>(p => preferred = p.Customer);
c.DoSomething();
Assert(preferred == c && c.IsPreferred);
}
这基本上是在贯彻好莱坞原则 (别打电话给我们,我们会打电话给你),因为你不打电话直接事件处理程序 - 而不是在引发事件时执行事件处理程序。
I'll likely want to use a centralised EventDispatcher. How could I get hold of one?
将其作为参数传入。
它可能看起来不像 EventDispatcher
,而是像一些以领域特定术语描述所需功能的领域服务。编写应用程序时,您可以选择要使用的服务实现。
域对象不应该有任何依赖关系,因此也没有依赖注入。然而,当从域对象中调度域事件时,我可能想要使用集中式 EventDispatcher
。我怎样才能得到一个?
我不想 return 向调用者发送事件列表,因为我希望它们保持不透明并保证它们的调度。这些事件只能由需要强制执行最终一致约束的其他域对象和服务使用。
你要求两者兼得。您要么需要注入依赖项,要么需要反转控制并让另一个对象管理器负责 Aggregate 和 EventDispatcher 之间的交互。我建议让你的聚合尽可能简单,这样它们就没有依赖性并且保持可测试性。
以下代码示例非常简单,不会成为您投入生产的代码示例,但它说明了如何设计无依赖性的聚合,而无需在需要它们的上下文之外传递事件列表。
如果您的聚合中包含事件列表:
class MyAggregate
{
private List<IEvent> events = new List<IEvent>();
// ... Constructor and event sourcing?
public IEnumerable<IEvent> Events => events;
public string Name { get; private set; }
public void ChangeName(string name)
{
if (Name != name)
{
events.Add(new NameChanged(name);
}
}
}
然后你可能有一个看起来像这样的处理程序:
public class MyHandler
{
private Repository repository;
// ... Constructor and dependency injection
public void Handle(object id, ChangeName cmd)
{
var agg = repository.Load(id);
agg.ChangeName(cmd.Name);
repository.Save(agg);
}
}
存储库如下所示:
class Repository
{
private EventDispatcher dispatcher;
// ... Constructor and dependency injection
public void Save(MyAggregate agg)
{
foreach (var e in agg.Events)
{
dispatcher.Dispatch(e);
}
}
}
基本上,您为域事件注册一个或多个处理程序,然后引发这样的事件:
public class Customer { public void DoSomething() { DomainEvents.Raise(new CustomerBecamePreferred() { Customer = this }); } }
并且将执行所有已注册的处理程序:
public void DoSomethingShouldMakeCustomerPreferred() { var c = new Customer(); Customer preferred = null; DomainEvents.Register<CustomerBecamePreferred>(p => preferred = p.Customer); c.DoSomething(); Assert(preferred == c && c.IsPreferred); }
这基本上是在贯彻好莱坞原则 (别打电话给我们,我们会打电话给你),因为你不打电话直接事件处理程序 - 而不是在引发事件时执行事件处理程序。
I'll likely want to use a centralised EventDispatcher. How could I get hold of one?
将其作为参数传入。
它可能看起来不像 EventDispatcher
,而是像一些以领域特定术语描述所需功能的领域服务。编写应用程序时,您可以选择要使用的服务实现。