【问题标题】:How should business errors be handled in a web application?在 Web 应用程序中应该如何处理业务错误?
【发布时间】:2010-10-26 15:26:03
【问题描述】:

假设我有一个可以将 Person 存储在数据库中的 Web 应用程序。每个人都必须有一个唯一的电子邮件地址(或用户名或其他)。如果用户尝试使用已存在的电子邮件地址添加人员,则应返回表单并显示错误消息(就像在典型的验证失败期间一样)。

这种错误最常见的方式是如何从服务层冒泡到控制器,然后再到视图?服务方法应该抛出异常让控制器捕获,还是返回一个值或某种结果对象?

如果我最终想使用我的服务层来生成 Web 服务,这会改变我的操作方式吗?

我们将不胜感激任何建议或最佳实践/示例应用程序的链接。

【问题讨论】:

    标签: web-applications error-handling spring-mvc


    【解决方案1】:

    我使用 Spring MVC 的方法是在我的 Validator 类中执行所有验证。因此,在我的验证方法中,我会调用服务上的一个方法来检查电子邮件地址是否已被使用。如果它是重复的,我会将该绑定的错误添加到 Errors 对象以显示在表单上。如果 Person 对象是有效的,那么您就不必担心在将其添加到服务时会引发期望。

    【讨论】:

    • 我最初考虑过这个......但出于某种原因,我认为验证逻辑是从模型中删除的一个步骤。也许是因为控制器通常调用验证器来验证表单输入。在这种情况下,我觉得这个避免重复的具体示例是一个业务规则,应该在业务层进行检查。想法?
    • 我想通过查看您问题的所有答案,这取决于上下文。就我而言,控制器是唯一将对象添加到服务的地方,因此在验证器中进行重复检查并不是什么大问题。但是,如果您说您可以通过 Web 服务提供业务层,那么您最好检查业务层中的重复项,因为这会被调用。
    • Boden,您的感觉是对的,应该在业务层检查业务规则。这样,无论您如何调用 Service 或 Model 方法(通过 MVC 控制器或 Web 服务),这些规则都会被检查。
    【解决方案2】:

    基本上有两种方法:异常和返回业务规则验证的结果。每种方式都有其优点和缺点,但基本上:

    例外:

    • 您一次只能返回一个失败的结果
    • 异常易于实现,不会使您的业务逻辑复杂化 - 只需检查条件并抛出业务异常
    • 例外仅适用于阻止规则
    • 打开的事务可以轻松回滚

    业务规则验证:

    • 您可以检查多个业务规则并将损坏的规则列表返回给用户
    • 多个规则会使您的逻辑流程复杂化,因为它们往往会混淆方法的返回类型
    • 它们允许处理更多场景,例如非阻塞信息规则

    我认为这在很大程度上取决于应用程序更适合哪种方法,因此没有简单的答案。很长一段时间以来,我成功地将例外用于业务规则,现在我倾向于利用第二种方法。

    【讨论】:

      【解决方案3】:

      我必须说我不是“验证错误是异常”模式的忠实拥护者。

      我的模型 Save() 方法只是创建并返回一个损坏规则列表(或一个空列表)。然后,我的 ControllerBase(或服务层)的工作就是处理那些他们认为合适的损坏规则。

      对于我的 ControllerBase,我通过 AddModelError 将它们一一添加到 MVC 验证处理程序中。在服务层,它最终就像在我的 JSON 结果中添加一个“isValid”标志。

      【讨论】:

      • 完全同意您关于验证错误和异常的说法。验证错误应该是可以预料到的,并且比抛出异常更优雅地处理。对于那些不同意的人,例如,我们在后端系统上处理 10,000 条记录,而不是通过 UI 处理单个记录。处理带有异常的验证错误是否仍然合适?我认为在这种情况下,您必须在元数据中记录有关失败的信息,因为您可能必须决定下游是否回滚事务以达到失败阈值。
      【解决方案4】:

      这个讨论由来已久,你会发现不同的阵营。我对此的看法是,如果您有一个具有明确独立功能的后端系统,您应该首先使用 API 和所有内容进行开发,然后从您的视图层使用该 API。在这种情况下,分层是必要的。一个例子是您正在为您已经开发的自定义软件编写一个 Web 前端。

      然而,在大多数 web 应用程序中,使用单一配置源更容易、更直接。 Django、RoR、JBoss Seam 等都采用这种方法。他们使用以一组域类为中心的概念,其中放置了所有验证逻辑和约束。模型异常(例如 NoSuchObject)直接映射到视图异常(404 Not Found),所有这些都由框架处理。使用这种方法,没有层,主要思想是避免分层,直到真正需要它。 Django 文档是有关该主题的真正信息来源,Seam 文档更具技术性,但也更详细。

      将某些逻辑公开为 Web 服务的决定并不能自动保证构建完整的分层解决方案的决定,它很容易重构,我想说的是 YAGNI 和 DRY 规则。

      分层确实增加了来回传播信息的另一个维度,我想说,除非它非常适合您的问题,否则不要使用它。

      【讨论】:

        【解决方案5】:

        我想我会选择抛出异常。您可以创建从 Exception 类派生的自定义异常。这样,您可以将类中的“消息”字段用作最终用户友好的消息,同时仍然能够从基础的“innerException”字段记录实际的异常详细信息。通过这种方式,您可以选择向用户隐藏您的方法为何失败的丑陋细节,同时仍保持基础异常描述的详细程度。

        【讨论】:

        • 告诉用户他们的电子邮件地址已被使用并不是一个“丑陋的细节”,这是一个易于检查的验证步骤。
        【解决方案6】:

        处理此问题的一种方法是为您的addPerson 操作创建一个具有状态代码的响应对象。例如:

        class AddPersonResponse {
            private Person person;
            private AddPersonStatus status;
            private AddPersonFailureReason failureReason;
        }
        
        enum AddPersonStatus {
            SUCCESS, FAILURE
        }
        
        enum AddPersonFailureReason {
            DUPLICATE_EMAIL_ADDRESS,
            DUPLICATE_USER_NAME
        }
        

        您发送给用户的视图可以绑定到服务返回的响应对象并给出适当的反馈。

        【讨论】:

        • 不喜欢状态码。我看到每次转身都会忽略返回值和状态码,导致后来当无效数据深入到后续业务逻辑时出现模糊的崩溃。
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2013-01-02
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2015-08-10
        • 1970-01-01
        • 2011-09-30
        相关资源
        最近更新 更多