【问题标题】:DDD - how to rehydrateDDD - 如何补水
【发布时间】:2017-05-26 05:49:49
【问题描述】:

问题:什么是最好的、有效的和面向未来的方法? 再水化存储库中的聚合?所提供方法的优缺点是什么?我的看法是否正确?

假设我们有一个聚合根,其中包含私有 setter 和用于访问 state 的公共 getter

行为是通过聚合根上的方法完成的。

指示存储库加载聚合。

目前我看到了几种可能的方法来实现这一点:

  1. 通过反射设置状态(手动或自动,例如。 自动映射器)
  2. 制作接受属性以便设置状态的构造函数
  3. 使用 state object 加载聚合

1) Jimmy Bogard 暗示他的工具 Automapper 不适用于双向映射。但有些人认为我们必须务实,以对您有帮助的方式使用工具。

对我来说,我不喜欢通过反射进行的充分补液。也许 Automapper 存在或聚合根以这样的方式弯曲,可以完成映射(参见他的文章中Vaughn 的一些 cmets)。

2) 创建 构造函数 用于再水化,并带有几个参数,以便以正确的方式对聚合体的状态进行再水化。

这对参数可以扩展(= 新构造函数)或定义可以更改。我喜欢这种方法,除了有一堆参数的部分。

3) state 是聚合根的属性。状态被封装在一个新对象中,该对象由存储库构建,然后提供给聚合根以进行适当的初始化。

有些人认为构建这个状态对象更多工作(新类,在实体和聚合根上公开状态属性以执行业务规则),但它提供了一种初始化状态的干净方法.

假设我们需要事件溯源,状态的加载是否类似于加载事件?状态对象是否提供了一种处理事件的方法?是不是更有未来感?

【问题讨论】:

  • 您对“未来证明”的定义是否以某种方式排除了 ORM,或者您是否将它们包含在“反射”中?因为这就是现在有多少人为他们的实体补充水分。
  • 我同意@Joseph 的观点:域是事实,不能通过反射改变,所以我们宁愿使用构造函数或状态来补充聚合。
  • “通过反射不可变” - 一切都通过反射可变:) 另外,你并没有真正回答我的问题。
  • @guillaume31:我们有用于未来证明的存储库。如果我们想使用 dapper、ado.net 或其他方式映射到数据存储,我们可以在具体的存储库实现中修复它。但是我们不喜欢通过反射设置状态的想法:)
  • 您可能需要准确解释“面向未来”的含义(是关于下一个流行方法还是代码的可维护性?),但我明白您的意思。虽然没有理想的解决方案,因为更改构造函数以适应补水场景也可能会扩大域层的误用范围。

标签: domain-driven-design aggregate automapper


【解决方案1】:

我会争辩说,试图过分面向未来代表了许多人陷入的陷阱,这会增加代码库的过度复杂性。在合理的架构决策和过度架构无法保证存在的问题的解决方案之间有一个很好的平衡。

话虽如此,我完全同意 Jimmy 所说的,关于 AutoMapper 不是用于双向映射的。您的域代表应用程序中的“真相”,不应直接可变。我从事过具有双向映射的项目,虽然它们确实有效,但有一种趋势是开始将域对象视为 DTO。当您开始拥有只读属性时,它会变得很痛苦,必须反思才能进行设置 - 是否使用工具。从 DDD 的角度来看,我们不应该允许外部影响简单地说明属性值应该是什么,因为随着时间的推移,它很可能会导致领域模型贫乏。

内部状态确实运作良好,但它们的代价是额外的开销和复杂性。正如您所提到的,有一个合理的权衡,因为您正在增加相当多的工作。但是,您可以利用该机会允许聚合在允许设置状态之前根据聚合中的自包含业务规则验证状态。这解决了我对双向映射的最大担忧。您至少可以强制状态对象包含有效数据,然后仅在其有效时才构造聚合。它也更具可测试性。我在这种方法中看到的最大问题是,您团队的技能水平将直接关系到正确使用这种方法的成功与否。可以说,复杂性并没有增加足够的价值来实现全域,因为您可能会拥有具有不同流失级别的聚合。我参与的几个项目都使用了这种方法,我发现与直接使用构造函数相比没有什么优势。

通常,在大多数情况下,我使用构造函数进行补液。它在不太复杂之间走这条线,加上它让聚合负责允许或禁止构建对象 - 再次,允许域控制水合尝试是否会导致有效的对象。对构造函数膨胀的一个很好的折衷方案是使用可变 DTO 作为构造函数的参数,本质上充当数据结构以随着时间的推移保持一致的构造函数签名。从本质上讲,它也有点面向未来。它采用了状态对象方法最吸引人的优势,即干净的签名,但移除了内部抽象的附加层。

您提到事件溯源是一种可能性。状态加载与您将要执行的操作完全不相似(在我看来)。使用状态对象,您可以在给定时间点对聚合的状态进行快照。使用事件溯源,您将重播事件,每个事件都代表改变状态所需的数据,而不是状态本身。因此,您的构造函数很可能是一个事件集合,代表一系列增量,以反复改变状态,直到达到当前状态。当您想要对聚合进行水合时,您将为它提供与该聚合相关的事件,并且它将重放它们以达到当前状态。这也是事件溯源的真正优势之一。您每次都在强制域对象的水合通过创建它们所需的业务逻辑。给定一个事件列表,聚合将通过以一致的方式应用事件来强制每个状态更改都是有效的,无论该事件是实时应用的,还是重放以达到当前状态。

回到面向未来的方面,因为它与事件溯源有关,当事件需要更改时,需要有意识的努力。由于您必须重播事件才能到达当前状态,因此您很可能必须弃用事件并在业务逻辑发生变化时启动新事件以转换到。您可能(读作“可能会”)发现自己的版本控制事件。您的聚合不仅需要了解当前的状态更改要求,还需要了解以前的状态更改要求。因此,如果您更改事件处理程序,则必须确保它对现有事件也有效。当您向事件添加附加数据时,通常不会涉及太多。但是,当您开始从事件签名中删除数据时,您会立即使该事件面临与早期结构不兼容的风险。同样,即使更改事件内部数据结构的名称也可能导致向后兼容性问题。如果您开始事件溯源,则无需像担心向后兼容性那样担心未来的验证。事件溯源很棒,但要为额外的复杂性做好准备。

【讨论】:

  • 我们正在以给定的三种方式(概念证明)构建模型。您的回答很有帮助,谢谢。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-05-20
  • 1970-01-01
  • 2020-12-23
  • 2018-06-01
  • 2022-10-06
相关资源
最近更新 更多