最近我读了很多关于总是有效的域实体的文章。我逐渐相信,为了确保这些实体始终有效,我需要:
1)删除原始痴迷,并将验证/域规则放入值对象构造函数中,如下所示:https://enterprisecraftsmanship.com/2016/09/13/validation-and-ddd/。2)在实体结构或属性设置器中放置验证/域规则,如下所述:http://gorodinski.com/blog/2012/05/19/validation-in-domain-driven-design-ddd/。
然而,我接着看了一些开源项目,比如这个项目:https://github.com/gregoryyoung/m-r。据我所知,这个项目的作者是始终有效的域模型的拥护者,但我在这里查看了InventoryItem类:https://github.com/gregoryyoung/m-r/blob/master/SimpleCQRS/Domain.cs。我注意到我能够做到这一点:
InventoryItem inventoryItem = new InventoryItem();或者这个:
InventoryItem inventoryItem2 = new InventoryItem(Guid.Empty,null);在我看来,这意味着实体是在无效状态下初始化的。在我最近看过的所有其他开源项目中,似乎都是这样的,例如,这个项目:https://github.com/dcomartin/DDD-CQRS-ES-Example/blob/master/src/Domain/Customer.cs。
我意识到在这些开源项目(https://martinfowler.com/bliki/ContextualValidation.html)中存在上下文验证。我还意识到,如果映射到域模型,ORMs需要一个默认的空构造函数。
如果使用零论证构造函数或空/空值初始化域对象时,它是否处于有效状态?
发布于 2018-06-12 19:55:32
在讨论对象创建中的注意事项之前,让我们首先讨论一下问题背后的动机:“始终有效的域实体”这一短语。这个短语充其量是误导的,在DDD的上下文中没有什么意义。这一点应该是显而易见的,原因有两个:
首先,它隐式地转移了您对系统行为的关注,相反,它要求您只从状态角度考虑验证。虽然从表面上看,这似乎是有意义的(当然,验证是根据状态!),但您必须记住,DDD的基本原则是根据行为对系统进行建模。原因很简单,在确定某个状态是否有效时,上下文或业务流程本身通常是一个重要的考虑因素。采用这种方法对系统进行建模,可以大大降低系统的复杂度。
这就引出了第二个原因,那就是这样一个制度所需要的实际要求。为了创建“始终有效的域实体”系统,需要根据使用状态的业务流程对每个状态的排列进行建模。一个简单的例子可以说明这方面的局限性:
规则:
Customer必须超过18岁才能注册Customer必须在25岁以下才有资格在注册时获得折扣。Customer必须在25岁以上才能预订首先要注意的是,所有这些规则(几乎与所有规则一样)都适用于某些业务流程。它们不是在真空中存在的。这些规则将在customer.Register()和customer.Reserve()上验证。这就产生了一个更具描述性和声明性的范例,因为规则在哪里执行是很清楚的。
如果我们想要对这些规则进行建模,使其生成“始终有效的域实体”系统,则需要将Customer划分为Registrar和Reserver实体。虽然对于本例来说,这似乎并不是很糟糕,但随着规则变得更加复杂和丰富,您最终将得到一个像这样的爆炸性类,它们表示“在”某个上下文或进程中的状态。这是没有必要的,当两个这样的对象依赖于同一个状态时,这将不可避免地产生问题。
此外,像Customer c = new Customer()这样的东西不是抛出异常的好地方,因为还不清楚哪些业务规则可能适用。这让我们进入了最后的讨论。
我只是想说,在构造函数中,业务规则应该是零验证的。对象构造与您的业务域一点关系都没有,因此,除了上述有关上下文和一致性的原因之外,所有业务规则都应该在实体的方法主体中强制执行(可能的方法是以业务流程命名的,对吗?)
此外,"newing“一个对象与在您的域中创建一个新实体并不是一回事。物体不是凭空冒出来的。如果有关于如何进入您的系统的新实体的业务规则,那么它应该在您的域中建模。下面是一个真正的http://udidahan.com/2009/06/29/dont-create-aggregate-roots/大师对这一主题的进一步讨论
发布于 2018-06-11 14:19:06
如果使用零论证构造函数或空/空值初始化域对象时,它是否处于有效状态?
这是可能的。
没有违反使用默认构造函数创建有效域对象的规则。
没有违反有关创建带有空值的有效域对象的规则。
创建缺少可选元素的有效域对象没有违反规则。
使用nulls创建有效的域对象没有违反规则。
出现问题的地方:创建不符合自己语义代数的域对象。
正确的实现取决于对语义的正确解释。
对领域概念采取宽容的表示并通过步骤应用额外的约束是正常的事情。这是易于添加类型的实现语言之一(例如: F#),它比Java这样笨拙的语言更有优势。
例如,如果我们有一个关心数字的域,并且在该域中有一些特定于素数或Mersenne素数的功能,那么使用的一种自然机制就是创建三种不同的类型;明确地说明要对解决方案不同部分的输入应用不同的约束集的概念。
Number n = new Number(...)
Optional<Prime> p = n.prime()
if (p.isPresent()) {
Optional<MersennePrime> mp = p.mersennePrime()
// ...
} 在这种设计中,这个数字实际上是一个Prime的“验证”将存在于Prime构造函数本身中。在需要参数为Mersenne素数这一附加约束的情况下,我们使用Prime -> MersennePrime转换,确保有关Mersenne约束的知识有一个权威(“不要重复自己”)。
“使含蓄的、明确的”
https://softwareengineering.stackexchange.com/questions/372338
复制相似问题