域对象不应该有任何依赖关系,因此也没有依赖注入。但是,从域对象内部调度域事件时,我可能需要使用集中式EventDispatcher
。我怎么能得到一个?
我不想将事件列表返回给调用方,因为我希望它们保持不透明并保证它们的调度。这些事件应仅由需要强制实施最终一致约束的其他域对象和服务使用。
查看乌迪达汉的域名事件
基本上,您为域事件注册一个或多个处理程序,然后引发如下事件:
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); }
这基本上是实现好莱坞原则(不要打电话给我们,我们会打电话给你(,因为你不直接调用事件处理程序 - 而是在引发事件时执行事件处理程序。
我可能想使用集中式的EventDispatcher。我怎么能得到一个?
将其作为参数传入。
它可能看起来不像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);
}
}
}