我已经阅读了很多内容,我仍然无法决定哪种方法更适合DDD中的域验证。
我们有两大抱负:
野心1 模型永远不会以无效状态结束。它的状态应该通过它的方法严格改变,反过来,如果传递了无效的参数,它将抛出异常。
我喜欢这种方法的方法是最终得到干净,有保障的状态。
我不喜欢的是,所有模型调用代码都会导致异常处理爆炸,这听起来不对。
野心2 放弃野心1,让模型最终处于无效状态但是在使用模型调用模型的代码中.IsValid()如果为false则执行model.GetErrors()并从那里开始。
这种方法可以杀死大量的例外情况,但是你必须明确检查错误是不是很好。
我还是无法理解。我希望有人可以提出一些令人信服的论据来帮助我解决这个问题。
答案 0 :(得分:1)
DDD倡导Ambition 1,因此实体或价值对象永远不会处于无效状态。你不喜欢这个是你必须处理异常。有很多方法可以正确处理异常。您可以让异常传播并在更高级别捕获它,也可以创建主域异常,以便更容易处理。但作为一般规则,如果出现问题(例外),应该注意它。
对于"野心2"我不认为这是一个好主意,因为在任何地方你都必须先处理一个你需要的对象来检查它是否有效,因为你无法保证这一点。而且我很容易变得比捕捉异常复杂得多。考虑一下您需要确保聚合中的每个对象都有效的聚合。
答案 1 :(得分:1)
这取决于您的背景,我认为有合理的理由使用这两种模式。有时看起来无效的状态'实际上在给定的上下文中是有效的,并且确定有效性的能力是一个有意义的域操作。
我们在最近的系统中采用的模式是有两个不同的上下文:
在第一个上下文中,数据在提交时填充到实体,实体包含确定有效性的业务规则 - 但有效性成为实体的状态。这允许API使用者提交数据,并检索与实体关联的资源,包括有效或无效状态和原因的有意义的表示。
如果实体有效,那么此上下文中的关键业务规则是允许状态转换(通过域事件)将命令发送到第二个上下文。
在第二个上下文中,不允许使用无效状态,并通过抛出异常来强制执行。
这种模式的价值在于,在第一个上下文中,我们收集用户提交的无效数据类型的数据,并为api消费者提供对其操作历史的持久访问权限,包括围绕无效数据捕获的记录以及为什么
但是在包含更有意义的业务逻辑的第二个上下文中,业务逻辑可以保持非常干净和清晰,因为一旦成功构造了实体,代码就可以采用有效状态。
答案 2 :(得分:1)
对于数据验证,你几乎总是希望在这里有一个非常明确的界限,这里是某种东西的代表,但我不知道它是否有任何好处和#34;和"这里表示满足所有使用约束的东西"。
一旦检查了一段不可变数据,就应该对其进行标记,以便所有消费者都知道它是好的。标记的常用机制是依赖于类型系统。 Money.amount
在内存中表示为不可变整数,但我们也保证它是非负数(或其他)。
让工厂返回一个必须检查其有效性的对象的替代方法是让它返回一个对象,该对象捕获对象可能无效的事实。
Try<Money> money = MoneyTree.showMeTheMoney(-7)
在此示例中, money
要么是Success<Money>
,要么MoneyTree能够对-7
做一些有用的事情,或者它将成为Failure
{{1}}。
In 2014, Maurício Linhares写了这个想法的摘要。谷歌会引导你到其他人。
蓝皮书中的战术模式来自于大约在2003年被理解为Java的最佳实践。特别是,Evans明确地提到了这样一个事实:编码模式应该允许开发人员使用域,而不是管道。异常倾向于是一个更好的选择,因此域语义不会在一堆错误处理背后丢失。
另一方面,对于域逻辑,异常是一个糟糕的选择;如果您正在建模的业务问题有多条路径;然后代码结构应该使这些路径显式 - 使用模型控制流的异常通常是一个坏主意,域驱动设计不包括任何需要它们的约束。