domain-driven-design - DDD - 如何补水

标签 domain-driven-design aggregate automapper

Question: what is the best, efficient and future proof way to rehydrate an aggregate from a repository? What are the pro's and con's of the provided ways and are my perceptions correct?



假设我们有一个带有私有(private) setter 的聚合根,但有用于访问状态的公共(public) getter

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

存储库被指示到 负载一个聚合体。

目前,我看到了几种可能的方法来实现这一目标:
  • 通过 设置状态反射 (手动或自动,例如。
    自动映射器)
  • 制作 构造函数接受属性以便设置状态
  • 使用 加载聚合state object

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

    对我来说,我不喜欢通过反射(reflection)来完全补水。也许 Automapper 存在或聚合根以可以完成映射的方式弯曲(参见他文章中 Vaughn 的一些评论)。

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

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

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

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

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

    最佳答案

    我会争辩说,过多地尝试面向 future 代表了许多人陷入的陷阱,这会增加代码库的过度复杂性。在合理的架构决策和对不能保证存在的问题的过度架构解决方案之间有一个很好的平衡行为。

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

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

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

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

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

    关于domain-driven-design - DDD - 如何补水,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/41590859/

    相关文章:

    java - 不要模拟值对象 : too generic rule without explanation

    c++ - 什么是聚合和 POD 以及它们如何/为什么特别?

    c# - Automapper 实例配置尚未在 xUnit 测试中初始化

    c# - Automapper 不与 .MapFrom 一起使用三元和计算值与 Net Core 2.2

    java - DDD (java) 聚合根和持久性

    node.js - 如果 wolkenkit(或任何)事件源事件是非结构化的,会有问题吗?

    c++ - 从 braced-init-list 初始化私有(private)聚合

    asp.net-core-mvc - 使用automapper设置创建时间和最后编辑时间

    c# - C#中异步调用的设计模式

    r - 面板数据中的每月协方差计算 -r