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