3

我已经阅读了 scott millett 的专业 asp.net 设计模式一书,在他的示例中,他在 Validate() 方法中验证了他的业务逻辑,如果任何破坏的规则被破坏,它们将被添加到集合中,并且服务层调用一个方法该模型称为 GetBrokenRules()。

现在我还阅读了几本关于 DDD 的书籍、博客和论坛,其中说在 DDD 中对象永远不应该进入无效状态。

我看到的所有关于 DDD 的示例都在业务规则被破坏时抛出错误,而不是传回一组被破坏的规则。我什至下载了 scott millett 的最新源代码,他现在更改了他的代码,该代码现在抛出错误,而不是传回被破坏的规则列表。我还看到了与其他 DDD 代码示例相同的方法。

我正在与一个团队成员进行辩论,他认为抛出错误是耗费资源的,我们不应该抛出错误,而是像我们目前所做的那样返回一组被破坏的规则。然而,通过这样做,我们传递了一个无效的对象,因为它有不可靠的数据,我们只在最后检查它的破坏规则。

我只是在想其他人对这个问题的看法。我们是否应该在业务规则失败时立即抛出错误?如果是这样,您能否强调这样做的任何利弊。我不知道 .net 中抛出错误的资源成本有多大,所以我不能反对这一点,但我想知道这是否也是个人意见而不是编码标准的问题。

麦克风

4

4 回答 4

4

异常只有在实际抛出时才昂贵。

由于您应该事先验证逻辑,因此异常应该很少见。如果不是这种情况,那么你做错了。

到目前为止,异常是通知模型处于无效状态的最佳机制。如果应用程序不终止,它们需要显式处理。

使用错误集合/返回值不是一个好习惯,因为这些可以(并且很多时候)被使用代码忽略。

于 2012-04-17T11:26:43.120 回答
1

@Oded 所说的完全正确。

然而,还有另一种处理验证的方法,不会抛出异常,也不会传递无效对象。简单地说,您总是通过工厂方法创建对象。如果一切正常,则返回一个实例,否则返回 null。当然,这意味着您必须在创建对象时检查一次 null ,但仅此而已。工厂方法(可以是服务的一部分)可以将 IValidationDictionary(不是 BCL 接口)作为参数,工厂可以在其上设置错误。但是我们在这里只讨论来自用户的输入数据(格式验证)。这里没有商业规则。如果对象将在其状态无效的上下文中使用,则它必须抛出异常。

您可以通过在对象上或其聚合根上使用方法(如 CanDoThat())来避免 99%。如果 object.CanDoThat(context) 这样做并避免异常。如果由于并发性而同时发生变化,则可能会触发异常。

我通常有以下工作流程:

  • 控制器层的用户输入验证(这是应用程序的反腐败层)。这里只处理数据格式。
  • 一旦数据有效,就会向前发送以进行处理(通常通过命令处理程序)
  • 在处理时,我询问聚合根是否可以执行特定操作。如果可以,一切都会向前发展。
  • 如果不能,则有2种情况:

    1. 很可能出现了错误,例如错误,因此此处适合例外(这是 99% 的情况)。
    2. 在这种情况下,这是一个有效的响应,所以它被简单地忽略了。
  • 当然,您可能会遇到聚合根级别一切正常的情况,但由于数据库约束,它在存储库级别失败。这意味着存储库会抛出一个异常,我会处理它(因为我预计这种情况会发生)。如果用户期望得到响应,那么我将抛出一个特定的异常来发出错误信号,该错误将设置 View 的 ModelState。

现在这是一个棘手的情况,因为看起来我正在使用异常来控制代码流,但事实并非如此。虽然我希望数据库抛出一个违反约束的异常,但它仍然是一个例外情况,因为这种情况并非每次都发生,而且这不是用户可以避免的情况。我接下来抛出特定异常,因为第一个异常与持久性相关,但我的控制器不知道它,所以我必须以它理解的方式传达该异常状态。

好的,很长的答案,但想法是一些错误,而预期的仍然是异常,它们需要被这样对待。但是,如果 ipnut 数据存在格式验证错误或输入数据以特定方式无效,则应在第一反腐败层中处理,控制器本身不需要异常。控制器不应让无效输入继续进行。如果它继续前进,那么它就是一个错误,一个特殊的情况。

希望,我不是很困惑:)

于 2012-04-17T12:34:23.020 回答
1

很好的讨论。它唤醒了旧的讨论主题。我测试了几种方法,发现混合使用是一个不错的选择。我在验证服务方法或 ctor 的输入参数时使用异常。如果验证知识不是实体责任的一部分,我将验证器与访问者模式一起使用来检查域实体。如果您是 Persistence-ignorance-religion(我是)的一部分,您可以使用位于基础设施层的验证器并验证它是否可以持久化(或者可能通过适配器导出到另一个系统......)。但是,我在我的实体上使用了很少的公共自动属性,只是为了使实体处于无效状态变得困难。我还让实体验证它自己的域范围/责任(尝试在此处遵循 SRP ......)。

抛出异常作为通知其他部分/层的一般解决方案可能并非对每种情况都是最佳的,但对于某些情况可能是最佳的。

/干杯

于 2012-04-18T06:23:20.480 回答
0

为什么不对破坏规则列表抛出异常?

与构建规则、弄清楚如何处理它们、告诉用户错误以及介于两者之间的所有网络流量的成本相比,异常几乎没有成本。

于 2012-04-18T02:07:23.200 回答