问题:什么是最好,最有效和面向未来的方法 从存储库中重新合成聚合?提供的方式有什么样的赞成和概念,我的看法是否正确?
假设我们有一个带私有设置器的聚合根,但是用于访问状态的公共getter
行为是通过聚合根上的方法完成的。
指示存储库加载聚合。
目前我看到了几种可能的方法来实现这一目标:
1)Jimmy Bogard暗示他的工具 Automapper 不适用于双向映射。但有些人认为我们必须务实,以一种帮助你的方式使用工具。
对我来说,我不喜欢通过反射进行完整的补液。也许Automapper会存在,或者聚合根会以这样的方式进行映射(参见他的文章中Vaughn的一些评论)。
2)创建构造函数以进行补液,并使用几个参数,以便以正确的方式重新合成聚合的状态。
这些参数可以扩展(=新构造函数)或定义可以更改。我喜欢这种方法,除了有一堆参数的部分。
3)状态是聚合根的属性。状态被封装在一个新对象中,该对象由存储库构建,然后被提供给聚合根以获得正确的init。
有些人认为构建此状态对象是更多工作(新类,实体上的状态属性暴露以及聚合根以强制执行业务规则),但它提供了一种初始化状态的简洁方法
假设我们需要事件源,加载状态是否类似于加载事件?状态对象是否提供了处理事件的方法?这是未来的证据吗?
答案 0 :(得分:7)
我认为,过多地考虑未来证明代表了许多人陷入的陷阱,这增加了代码库的复杂性。在合理的架构决策和过度架构解决问题的解决方案之间存在一种良好的平衡行为。
话虽如此,我完全赞同Jimmy所说的,关于AutoMapper不是用于双向映射。您的域名代表了真相"在您的应用程序中,不应该是直接可变的。我曾经研究过具有双向映射的项目,虽然它们确实有效,但仍然倾向于将域对象视为DTO。当你开始拥有只读属性时,它会变得很痛苦,不得不反思你的设置 - 工具与否。从DDD的角度来看,我们不应该允许外部影响简单地说出属性值应该是什么,因为它会随着时间的推移导致贫血领域模型,很可能。
内部状态确实运行良好,但它们需要额外的开销和复杂性。正如你所提到的那样,有一个合理的权衡,因为你正在增加相当多的工作量。但是,您可以利用该机会允许聚合在允许设置状态之前根据聚合中的自包含业务规则验证状态。这解决了我对双向映射的最大担忧。您至少可以强制执行状态对象包含有效数据,然后仅在聚合有效时构造聚合。它也更容易测试。我在这种方法中遇到的最大问题是,团队的技能水平将直接影响到正确使用这种方法的成功。可以说,复杂性并没有为实现域范围增加足够的价值,因为您可能会有具有不同流失水平的聚合。我参与的一些项目使用了这种方法,而且我发现直接构造函数的使用没有什么优势。
通常,在大多数情况下,我使用构造函数进行补液。它在不过度复杂之间走了一条路,加上它使聚合体承担允许或不允许构造对象的责任 - 再次允许域控制水合尝试是否会产生有效对象。对构造函数膨胀的一个很好的折衷是使用可变DTO作为构造函数的参数,本质上充当数据结构以随时间维护一致的构造函数签名。在这个本质上,它也有点面向未来。它采用了状态对象方法最有吸引力的特权,即干净的签名,但删除了内部抽象的附加层。
你提到事件采购作为一种可能性。状态加载与你将要做的完全不同(在我看来)。使用状态对象,您可以在给定时间点快照聚合的状态。通过事件源,您将重放事件,每个事件代表改变状态所需的数据,而不是状态本身。因此,您的构造函数可能是事件的集合,表示重复变异状态的一系列增量,直到达到当前状态。当你想要为你的聚合提供水合时,你将为它提供与该聚合相关的事件,并且它将重放它们以达到当前状态。这也是事件采购的真正优势之一。您每次都强制域对象的水化以完成创建它们所需的业务逻辑。给定一个事件列表,聚合将通过以一致的方式应用事件来强制执行每个状态更改,无论事件是实时应用还是重播以达到当前状态。
回到面向未来的方面,因为它涉及事件采购,当事件需要改变时需要有意识的努力。由于您必须重放事件才能进入当前状态,因此您很可能不得不弃用事件并将新事件转换为业务逻辑更改。您可以(读作"可能会")发现自己的版本控制事件。您的聚合不仅需要了解当前的状态更改要求,还需要了解以前的状态更改要求。因此,如果更改事件处理程序,则必须确保它对现有事件也有效。在向事件添加其他数据时,通常不会涉及太多。但是,当您开始从事件签名中删除数据时,您会立即使该事件面临与早期结构不兼容的风险。同样,即使更改事件内部的数据结构的名称也可能导致向后兼容性问题。如果您开始采购事件,则无需担心与向后兼容性相关的未来验证。事件采购很棒,但要做好准备,以增加复杂性。