我正在尝试领域驱动设计和事件溯源。我计划使用(用C#开发(NServiceBus,JOliver的EventStore和NES来绑定它们。我已经拥有适用于简单情况的基础架构(一个仅包含值对象的聚合根(。
我正在阅读埃文斯蓝皮书,我正在尝试开发一个简单的领域模型,其中的例子来自我的工作领域(HVAC维护公司的ERP和CRM(。
我正在建模一个简单的子领域,即HVAC机器以及它们之间的关系。机器有多种类型,例如熔炉、燃烧器、空调、压缩机、通用组件。每台计算机可以有多个子计算机。所有计算机类型共享一些通用数据和一些常见行为。但是每种类型都有额外的数据和特定的行为,例如,您只能将燃烧器对象添加到熔炉。
我分析的第一个结果是,每台机器都应该是一个聚合根(继承自 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
}
但我有一些怀疑:
这是表示我的域的正确方法吗?我读到不鼓励类继承,但在我看来,这是使用它的完美案例(燃烧器是机器,熔炉也是如此(。我将仅限于一个继承级别。
是否可以使用事件溯源实现类继承?特别是提议的技术堆栈(nServiceBus,EventStore,NES(?
我应该如何执行子机器的添加(例如,燃烧器到炉子(?此操作可以分为两个:
- 将新的刻录机添加到刻录机存储库。
- 将对燃烧器的引用添加到父炉的燃烧器列表中但是这两个操作全局修改了两个聚合根,因此该操作应在两个单独的命令处理程序/事务中执行...但第二个取决于第一个...这是某些建模错误的证据吗?我可以将 nServicebusMessages 批处理在一起以在单个事务中执行操作,但我读到这不好......
,则父计算机将丢失子计算机列表(验证需要该列表(,我无法在事件溯源存储库中查询 Guid 以外的其他属性。
提前感谢您对讨论的任何贡献,
对于#1,我会说不。 示例中的聚合根应为"熔炉"(仅(。 燃烧器应建模为炉子上的集合,就像您当前拥有的那样,但不应该是聚合根。 我不认为继承本身是一个问题,除了你的刻录机现在通过继承是一个聚合根(因为刻录机=>机器=>聚合基础(。
如果燃烧器仅存在于熔炉的上下文中,您可能不需要燃烧器存储库 - 您将始终向熔炉添加燃烧器。 我不清楚创建新燃烧器本身是否有趣,并且需要自己的事件。 该问题的答案将决定您是同时需要两个事件还是只需要"已添加"事件。
-
我会避免继承。相反,请创建单个计算机聚合并创建此计算机将引用的描述符对象。描述符将是一个值对象,可以区分不同的计算机类型。如果绝对必要,请对此对象使用继承,但不要将继承用于聚合本身。
-
对 ES 使用类继承的能力仅受序列化程序的限制。ES不规定数据的序列化方式,但是如果您使用Protobuf或Newtonsoft.Json之类的东西,它们支持继承。但是,在 JSON 情况下,使用继承确实会在 JSON 输出中放置$type属性。
-
这取决于燃烧器是否需要独立于熔炉而存在。如果不是,则它是熔炉聚合的值对象或实体部分,应与熔炉一起完整保留。如果是,那么它必须是聚合体,应首先创建,然后添加到熔炉中。这可以通过 NSerivceBus saga 来实现,其中 AddBurnerToFurnaceCommand 通过首先启动相应的 saga(发送命令来创建 Burner(来处理。创建燃烧器后,在燃烧器和熔炉之间创建关联。熔炉只会通过 Guid 引用燃烧器。在 ES 中,所有查询通常都通过投影处理,并且只有行为按 ID 调用规范聚合的事件存储。