【问题标题】:DDD: inheritance and transactionsDDD:继承和事务
【发布时间】:2013-02-15 11:10:15
【问题描述】:

我正在试验领域驱动设计和事件溯源。我打算使用(用 C# 开发)NServiceBus、JOliver 的 EventStore 和 NES 来绑定它们。我已经为一个简单的案例工作了基础设施(一个聚合根,只有值对象)。

我正在阅读 Evans 蓝皮书,并且正在尝试开发一个简单的领域模型,其中的示例取自我的工作领域(HVAC 维护公司的 ERP 和 CRM)。

我正在建模一个简单的子域,即 HVAC 机器和它们之间的关系。机器有多种类型,例如熔炉、燃烧器、空调、压缩机、通用部件。每台机器可以有多个子机器。所有机器类型都有一些共同的数据和一些共同的行为。但每种类型都有额外的数据和特定的行为,例如您可以只将 Burner 对象添加到 Furnace。

我分析的第一个结果是每台机器都应该是一个聚合根(继承自 NES 中的 AggregateBase),因为它必须能够保存对特定机器的引用(例如,用于插入涉及单台机器的维修记录,故障记录等),也可以减少大型机器树中的并发问题。

因此,我的假设如下:

public class Machine : AggregateBase
{
    public DateTime InstallationDate { get; private set; }

    public Guid ManufacturerId { get; private set; }

    public Guid ModelId { get; private set; }
}

public class Furnace : Machine
{
    public List<Burner> burners { get; private set; }

    // other furnace properties

    public void AddBurner(Burner burner)
    {
        // perform validation
        this.Apply<BurnerAdded>(x=> x.burnerAdded = burner);
    }

    public void Handle(BurnerAdded @event)
    {
        this.burners.Add(@event.burnerAdded);
    }
}

public class Burner : Machine
{
    // burner specific properties/methods
}

但我有一些疑问:

  1. 这是代表我的域的正确方法吗?我读到不鼓励类继承,但这在我看来是使用它的完美案例(燃烧器是机器,炉子也是)。我将仅限于一级继承。

  2. 是否可以使用事件溯源来实现类继承?特别是建议的技术堆栈(nServiceBus、EventStore、NES)?

  3. 我应该如何执行添加子机器(例如燃烧器到炉子)?这个操作可以分为两种:

    1. 将新的 Burner 添加到 Burner 存储库。
    2. 将燃烧器的引用添加到父炉的燃烧器列表中 但是这两个操作全局修改了两个聚合根,所以操作应该在两个单独的命令处理程序/事务中执行......但是第二个取决于第一个......这是一些建模错误的证据吗?我可以将 nServicebusMessages 批处理在一起以在单个事务中执行操作,但我读到这不好...

    如果我让子机器引用父机器,则父机器会丢失子机器列表(验证所需),我无法查询事件源存储库以获取除 Guid 之外的其他属性。

提前感谢您对讨论的任何贡献,

【问题讨论】:

标签: domain-driven-design nservicebus event-sourcing ddd-repositories neventstore


【解决方案1】:

对于#1,我会说不。您示例中的聚合根应该是 Furnace (仅)。 Burner 应该被建模为 Furnace 上的一个集合,就像你目前拥有的那样,但不应该是一个聚合根。我不认为继承本身是一个问题,除了你的刻录机现在是通过继承的聚合根(因为 Burner => Machine => AggregateBase)。

如果燃烧器仅存在于熔炉的上下文中,您可能不需要燃烧器存储库 - 您将始终将燃烧器添加到熔炉。我不清楚创建一个新的燃烧器本身是否有趣,并且需要它自己的事件。该问题的答案将决定您是否需要这两个事件或只需要“添加”事件。

【讨论】:

  • 感谢您的帮助。问题是燃烧器独立于熔炉而存在,它只是一个可以在熔炉之间切换的组件(所以它肯定有一个身份),并且可以被其他域对象直接引用(例如,服务事件)跨度>
【解决方案2】:
  1. 我会避免继承。相反,创建单个 Machine 聚合并创建该机器将引用的描述符对象。描述符将是一个可以区分不同机器类型的值对象。如果绝对必要,请对这个对象使用继承,但不要对聚合本身使用继承。

  2. 通过 ES 使用类继承的能力仅受序列化程序的限制。 ES 没有规定数据如何序列化,但如果您使用 Protobuf 或 Newtonsoft.Json 之类的东西,它们支持继承。但是,在 JSON 的情况下,使用继承确实会将 $type 属性放在 JSON 输出中。

  3. 这取决于燃烧器是否需要独立于熔炉而存在。如果不是,那么它是 Furnace 聚合的值对象或实体部分,应该与 Furnace 一起完整地持久化。如果是,那么它必须是一个聚合,并且应该首先创建然后添加到 Furnace。这可以通过 NSerivceBus saga 来实现,其中 AddBurnerToFurnaceCommand 通过首先启动相应的 saga 来处理,该 saga 发送创建 Burner 的命令。创建 Burner 后,创建 Burner 和 Furnace 之间的关联。熔炉只会引用 Guid 的燃烧器。在 ES 中,所有查询通常都通过投影处理,只有行为通过 ID 调用规范聚合的事件存储。

【讨论】:

  • 感谢您的帮助。是的,燃烧器独立于熔炉而存在,它只是一个可以在熔炉之间切换的组件(所以它肯定有一个身份),并且可以被其他域对象直接引用(例如,服务事件)
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2014-07-21
  • 1970-01-01
  • 2019-08-23
  • 2015-11-17
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多