【问题标题】:DDD, Object graphs and Event SourcingDDD、对象图和事件溯源
【发布时间】:2013-01-24 03:13:01
【问题描述】:

简介

这个问题是关于 DDD 和事件溯源,其中聚合中的实体(聚合根除外)具有事件生成行为。

示例

下面是我描述的情况的一个示例,我确定我想在聚合内的其他实体中封装一些逻辑。这可能涉及暂停对实际示例以及它是否是一个好模型的怀疑。 :)

我正在尝试为 DeliveryRun Aggregate Root (AR) 建模,这是车辆执行交付的行程。在它离开之前,它必须有一个最新的DeliveryManifest。它的“最新性”向我表明DeliveryManifest 是AR 定义的DeliveryRun 一致性边界内的一个实体。

到目前为止还好。

我为此使用了事件溯源方法 - Greg Young 教授并在 Regalo library 中实施的方法。这意味着如果 AR (DeliveryRun) 没有任何行为,则它们实际上不需要任何实体(例如,SalesOrder 可能没有 SalesOrderLines,因为它记录了诸如 ItemsAdded/ItemsRemoved 之类的事件) .

但是,DeliveryManifest 周围有一些逻辑。具体来说,一旦首次请求清单,当项目被添加到交付时,需要创建清单的新版本。这意味着我们可以确保司机不会在没有可用的最新清单的情况下离开。

如果我要将逻辑封装在 DeliveryManifest 对象内(不会被序列化和存储;我们使用的是事件源,它不是 AR),我如何捕获事件?

我正在考虑的选项

  • 事件是否应该由 DeliveryManifest 实体生成,但保存在 DeliveryRun 本身(然后需要知道如何在从事件存储加载时将这些事件重播到 DeliveryManifest 中) ?

  • 是否应该没有DeliveryManifest(可能作为数据结构除外)并且所有逻辑/事件都由DeliveryRun直接实现?

  • DeliveryManifest 是否应该是它自己的 AR 并确保 DeliveryRun 被告知当前清单的 ID?由于这将清单对象置于DeliveryRun 的一致性边界之外,我需要构建一些事件处理来订阅与清单相关的DeliveryRun 中的更改,以便可以相应地更新/无效等。

  • 实现与 Udi 的 DomainEvents 模式类似的不同样式来捕获事件。这意味着更改 Regalo 库,尽管我认为它可以很容易地支持这两种模式。这将允许捕获聚合中所有实体生成的所有事件,以便针对 AR 保存它们。我需要考虑一个加载/重播的解决方案......

【问题讨论】:

  • 想解释一下软件如何防止送货卡车司机带着过期的清单离开?您可能希望减轻它曾经发生的风险,但不阻止它。此外,您可能想要一本记录实际发生的事情(即使清单是错误的,在特定的运行中,您想知道哪个清单被带走了)与计划发生的事情。
  • 经过反思,在我看来,manifest 和 run 的生命周期是不同的,但恰好在某个时间点重叠。在实际交付开始之前,清单可能会多次更改。计划交付运行时,您可能会指示应携带哪个清单。也许人们应该在计划交付后停止对清单进行更改(也许没有,或者在实际交付发生之前的某个时间点,人们可以更改清单)。显然,我缺乏领域知识......
  • 好的,所以我希望在涉及 DDD 的问题上得到这样的 cmets,但实际上我的问题是基于我已经正确建模的假设,因此我该如何实现封装AR 以外的实体中的逻辑?如果我是在我的 AR 上的行为方法中编写 C# 代码,它是否可以委托给其他封装逻辑的实体类?如果是这样,如何捕获这些实体生成的事件?如果它们自己不生成任何事件,那么这些事件是从哪里来的?

标签: oop domain-driven-design event-sourcing


【解决方案1】:

除非是一致性边界,否则我会避免将 DeliveryManifest 设为另一个聚合根。

许多示例无法解决此问题。聚合根似乎应该负责从其中的实体收集事件,并将它们分发给正确的实体以便稍后加载,这似乎是您的选择 1。

如果没有与 DeliveryManifest 相关的行为,选项 2 也非常好。

【讨论】:

  • 正如 Yves 指出的那样,确实需要对用例进行更多分析才能知道采用哪种方法,但假设您为您的域做出了适当的选择,您的选择似乎对应于:(1) -实现为实体,(2)实现为值对象,(3)实现为单独的聚合根
【解决方案2】:

机械式答案...您可以在其中构思出许多变化。基本上,您必须决定谁将收集所有这些事件:根(此处显示)或每个实体(此处未显示的方法)分别收集。从技术上讲,您有很多选项可以实现如下所示的观察行为(想想 Rx、手动编码的调解器等)。我将大部分基础架构代码呈现到实体中(此处缺少抽象)。

public class DeliveryRun {
  Dictionary<Type, Action<object>> _handlers = new Dictionary<Type, Action<object>>();
  List<object> _events = new List<object>();

  DeliveryManifest _manifest;

  public DeliverRun() {
    Register<DeliveryManifestAssigned>(When);
    Register<DeliveryManifestChanged>(When);
  }

  public void AssignManifest(...) {
    Apply(new DeliveryManifestAssigned(...));
  }

  public void ChangeManifest(...) {
    _manifest.Change(...);
  }

  public void Initialize(IEnumerable<object> events) {
    foreach(var @event in events) Play(@event);
  }

  internal void NotifyOf(object @event) {
    Apply(@event);
  }

  void Register<T>(Action<T> handler) {
    _handlers.Add(typeof(T), @event => handler((T)@event));
  }

  void Apply(object @event) {
    Play(@event);
    Record(@event);
  }

  void Play(object @event) {
    Action<object> handler;
    if(_handlers.TryGet(@event.GetType(), out handler)) {
      handler(@event); //dispatches to those When methods
    }
  }

  void Record(object @event) {
    _events.Add(@event);
  }

  void When(DeliveryManifestAssigned @event) {
    _manifest = new DeliveryManifest(this);
    _manifest.Initialize(@event);
  }

  void When(DeliverManifestChanged @event) {
    _manifest.Initialize(@event);
  }
}

public class DeliveryManifest {
  Dictionary<Type, Action<object>> _handlers = new Dictionary<Type, Action<object>>();
  DeliveryRun _run;

  public DeliveryManifest(DeliveryRun run) {
    _run = run;
    Register<DeliveryManifestAssigned>(When);
    Register<DeliveryManifestChanged>(When);
  }

  public void Initialize(object @event) {
    Play(@event);
  }

  public void Change(...) {
    Apply(new DeliveryManifestChanged(...));
  }

  void Register<T>(Action<T> handler) {
    _handlers.Add(typeof(T), @event => handler((T)@event));
  }

  void Play(object @event) {
    Action<object> handler;
    if(_handlers.TryGet(@event.GetType(), out handler)) {
      handler(@event); //dispatches to those When methods
    }
  }

  void Apply(object @event) {
    _run.NotifyOf(@event);
  }

  void When(DeliveryManifestAssigned @event) {
    //...  
  }

  void When(DeliveryManifestChanged @event) {
    //...  
  }
}

附:我把这个“乱七八糟”编码了,请原谅我的编译错误。

【讨论】:

  • 太好了,谢谢!我目前正在寻找一种方法,其中 AR 将自身作为 EventSource 传递到实体中。然后实体以与我迄今为止一直在构建的 AR 类似的方式调用 Record(),但该 impl 实际上只是调用 AggregateRoot.Record(),因此所有事件都针对 AR 进行记录。重放通过 Regalo 中的一个巧妙技巧解决,它将查找并调用事件及其所有基类的 Apply() 方法。如果你愿意,一种方法“继承”。如果我决定坚持下去,我会在 Vita 中构建一个示例来展示它是如何工作的。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-02-01
  • 1970-01-01
相关资源
最近更新 更多