问题:什么是最好,高效和面向未来的方法
从储存库中补充骨料?所提供方式的优缺点是什么,我的看法正确吗?


假设我们有一个聚合根,其中包含私有设置者,但公共设置者可以访问状态

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

指示存储库加载聚合。

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


通过反射设置状态(手动或自动,例如
自动贴图)
使构造函数接受属性,从而设置状态
state object加载聚合


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

对我来说,我不喜欢通过反射来全面补水。可能Automapper已有存在或聚合根被弯曲,从而可以完成映射(请参见他的文章中Vaughn的一些评论)。

2)使用两个参数创建用于补水的构造函数,以便以正确的方式补水骨料的状态。

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

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

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

假设我们需要事件源,状态的加载是否类似于加载事件?状态对象是否提供处理事件的方式?是否更适合未来?

最佳答案

我认为,过多地面向未来的做法代表了许多人陷入的陷阱,这给代码库增加了不必要的复杂性。在合理的架构决策与过度架构无法保证存在的问题的解决方案之间存在一种很好的平衡。

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

内部状态确实可以很好地工作,但是它们以额外的开销和复杂性为代价。如您所述,这是一个合理的折衷,因为您要添加大量的工作。但是,您可以利用此机会允许聚合在允许设置状态之前根据聚合中的独立业务规则验证状态。这解决了我对双向映射的最大关注。您至少可以强制状态对象包含有效数据,然后仅在有效时构造聚合。它也是可测试的。我用这种方法看到的最大问题是,您团队的技能水平将与正确利用此方法的成功直接相关。有人可能会争辩说,复杂性并没有增加足够的价值来实现整个域的范围,因为您可能会产生具有不同流失级别的聚合。我参与的几个项目都使用了这种方法,与直接使用构造函数相比,我发现没有什么优势。

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

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

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

08-04 09:55