【问题标题】:Where do you perform your validation?您在哪里进行验证?
【发布时间】:2009-06-01 19:18:32
【问题描述】:

希望您能在下面的场景中看到我所描述的问题。如果不清楚,请告诉我。

你有一个分为三层的应用程序,

  • 前端UI层,可以是asp.net webform,也可以是window(用于编辑Person数据)
  • 中间层业务服务层,编译成dll(PersonServices)
  • 数据访问层,编译成dll(PersonRepository)

在我的前端,我想创建一个新的Person对象,根据用户在UI中输入的内容设置一些属性,例如FirstName,LastName,然后调用PersonServices.AddPerson,传递新创建的Person . (AddPerson 不必是静态的,这只是为了简单起见,在任何情况下,AddPerson 最终都会调用 Repository 的 AddPerson,然后它将数据持久化。)

现在我想听听您的意见的部分是验证。在某个地方,需要验证新创建的 Person 。您可以在客户端执行此操作,这很简单,但如果我想在我的 PersonServices.AddPerson 方法中验证 Person 怎么办。这将确保我要保存的任何人都将得到验证,并消除对 UI 层执行工作的任何依赖。或者,在 UI 和业务服务器层中进行验证。到目前为止听起来不错吧?

因此,为简单起见,我将更新 PersonService.AddPerson 方法以执行以下验证检查 - 检查 FirstName 和 LastName 是否不为空 - 确保我的存储库中不存在这个新人

如果所有验证通过并且 Person 被持久化,此方法将返回 True,如果验证失败或 Person 未被持久化,则返回 False。

但是 AddPerson 返回的这个布尔值不足以让我在 UI 层向用户明确说明保存过程失败的原因。那么孤独的开发者该怎么办呢?最终,我希望 AddPerson 方法能够确保它要保存的内容是有效的,如果不是,能够将它无效的原因传达给我的 UI 层。

只是为了让你的汁液流动起来,解决这个问题的一些方法可能是:(在我看来,其中一些解决方案很糟糕,但我只是把它们放在那里,这样你就可以了解我想要做什么解决)

  • AddPerson 不是返回布尔值,而是返回一个 int(即 0 = 成功,非零等于失败,数字表示失败的原因。

  • 在 AddPerson 中,当验证失败时抛出自定义异常。每种类型的自定义异常都会有自己的错误消息。此外,每个自定义异常都足够独特,可以在 UI 层中捕获

  • 让 AddPerson 返回某种自定义类,该类具有指示验证是通过还是失败的属性,如果确实失败了,原因是什么

  • 不确定这是否可以在 VB 或 C# 中完成,但将某种属性附加到 Person 及其基础属性。这个“附加”属性可能包含验证信息等内容

  • 在此处插入您的想法或模式

  • 这里可能还有一个

对于这个冗长的问题,我深表歉意,但我绝对想听听您对此的看法。

谢谢!

【问题讨论】:

    标签: validation architecture


    【解决方案1】:

    多层验证与多层应用相得益彰。

    UI 本身可以进行最简单、最快捷的检查(是否存在所有必填字段,是否使用适当的字符集等),以便在用户输入错误时立即提供反馈。

    然而,业务逻辑应该承担大部分的验证责任......如果这是“重复的”,即如果业务层重新检查应该已经在UI - BL 应该检查所有业务规则(这对 UI 的正确性进行双重检查,启用多个不同的 UI 客户端,这些客户端可能在检查中并不完美——例如,智能手机上的特殊客户端可能没有良好的 javascript,等等——并且,有点,防止被恶意黑客入侵的客户端)。

    当业务逻辑将“已验证”的数据保存到 DB 时,该层 应该执行自己的检查 -- DB 擅长这一点,并且再次不必担心重复-- 强制数据完整性是数据库的工作(您可能希望有一天以不同的方式向其提供数据,例如“批量加载器”从另一个来源导入多个人员,这是确保所有这些方法的关键加载数据始终遵守数据完整性规则);某些规则(例如唯一性和参照完整性)确实在 DB 中得到了最好的执行,尤其是出于性能原因。

    当数据库向业务层返回错误消息(未作为约束 X 插入的数据将被违反)时,业务层的工作是以业务术语重新解释该错误并将结果提供给 UI 以通知用户;当然,BL 必须同样向 UI 提供关于违反业务规则的清晰完整的信息,再次显示给用户。

    因此,“自定义对象”显然是“唯一可行的方法”(例如,在某些情况下,我只是将其设为 JSON 对象)。当数据库拒绝持久化时,保留 Person 对象(以保持其“验证问题”属性)看起来不像是一种简单明了的技术,所以我不认为这个选项太多;但是如果您需要它(例如,启用“再次告诉我出了什么问题”功能,也许如果客户端在响应准备好之前离开并且需要稍后顺利重新启动;或者,此类对象的列表以供以后审核,&c) ,那么“自定义验证失败对象”也可以附加到该列表中......但这是一个“次要问题”,主要是让 BL 用这样的对象(如果插入确实成功,它也可用于提供有用的非错误信息)。

    【讨论】:

    • 可测试性怎么样?考虑一个人为的例子,它是我在一个不相关的应用程序中看到的过度简化的版本。 “Shopping.com”使用其内部网络应用程序“Genie”在其网站上创建优惠券/促销。可以创建的最短促销精灵在发布后的 6 小时内有效。要创建(而不是发布)促销,genie 会进行 api 调用,将 expiryDateTime 等发送到后端。请注意,由于某些充分的理由,不能更改 6 小时营业规则.....(请参阅下一条评论。)
    • 在 genie 中,我们想测试促销到期时是否在促销下方显示消息框。我考虑了当我们使用以下两种设计方法来创建促销时对测试的影响 - ONE - UI 检查 expiryDateTime 是否距离当前时间至少 6 小时或更长时间 &不到一个月的时间。然后它通过 expiryDateTime 到后端。 - 后端仅检查 expiryDateTime 是否介于当前时间和一个月之间。 TWO - 后端检查 expiryDateTime 是否在从现在起 6 小时到一个月之间.....(请参阅下一条评论。)
    • 在两种方法中测试 - ONE - 使用对后端的 api 调用将 expiryDateTime 设置为从当前时间开始的几分钟。 TWO - 向后端发送 api 调用并等待 6 小时以测试到期时间。或者,如果允许,直接点击数据库层并在那里更改 expiryDateTime。
    【解决方案2】:

    只是一个简短的(希望有帮助)评论:当您想知道在哪里放置验证时,试着假装很快,您将使用您还不熟悉的技术完全重新创建您的 UI 层**。尝试将任何您确定必须在新技术中重写的类似验证的业务逻辑排除在该层之外。

    您会发现例外情况 - 业务逻辑最终会出现在您的 UI 层中,但这仍然是一个有用的考虑因素。

    ** 移动开发、Silverlight、Voice XML 等等 - 假装您不了解您的“新”UI 层的技术有助于您抽象您的关注点并减少对实现细节的困扰。

    【讨论】:

      【解决方案3】:

      唯一重要的一点是:

      • 从前端的角度来看,中间层必须执行 all 验证,您永远不知道是否有人会尝试通过直接与您的对话来绕过您的前端验证中间层(无论出于何种原因)
      • 中间层可以选择将部分验证委托给 DB 层(例如数据完整性约束)
      • 您可以选择在 UI 中复制一些验证,但这只是为了提高性能(避免在常见情况下往返于中间层,例如缺少必填字段、格式错误的数据等)这些检查应该绝不代替在中间层进行检查

      【讨论】:

        【解决方案4】:

        应在所有三个级别上进行验证。

        当我在一个项目中时,我假设我正在制作一个框架,但大多数情况下并非如此。每一层都是独立的,在进行操作之前必须检查所有层的输入

        每个级别都可以有不同的执行方式,它们不必都使用相同的,但理想情况下,它们应该都使用相同的验证并能够自定义验证。

        您永远不想让不良数据进入数据库。所以你永远不能相信你从业务层获得的数据。需要检查。

        在业务层中,您永远不能信任 UI 层,您必须检查它以防止对数据库层的不必要调用。 UI 层的工作方式相同。

        【讨论】:

          【解决方案5】:

          我不同意 David Basarab 的评论,即所有层都应该存在相同的验证。出于一个原因,这违背了层的责任范式。其次,尽管主要目的是使层(或组件)松散耦合,但赋予层一定程度的责任(以及因此的信任)也很重要。尽管可能需要在 UI 和业务层中重复一些验证(因为 UI 层可以被黑客尝试绕过),但是不建议在每一层中重复验证。每一层都应该只执行他们负责的那些验证。在所有层重复验证的最大缺陷是代码冗余,这可能导致维护噩梦。

          【讨论】:

            【解决方案6】:

            其中很多都是风格而非实质。我个人倾向于将返回状态对象作为一种灵活且可扩展的解决方案。我想说我认为有几类验证在起作用,第一类是“这个人的数据是否符合一个人的契约?”第二个是“这个人的数据是否违反了数据库中的约束?”我认为第一次验证可以而且应该在客户端完成。第二个应该在中间层完成。通过这种划分,您可能会发现保存失败的唯一原因是 1) 违反唯一性约束,或 2) 某些灾难性事件。然后,您可以为第一种情况返回 false,并为另一种情况抛出异常。

            【讨论】:

              【解决方案7】:

              如果 R 层比 S 层更靠近用户(或任何您无法控制的输入流),则 S 层应该验证从 R 层接收到的所有数据。这并不意味着 R 层不应该验证数据。如果 GUI 在尝试新事务之前警告他犯了错误,对用户来说会更好。但是,无论您的 GUI 中的验证多么安全,下一层都不应该相信任何验证已经发生。

              这假定您的数据库完全在您的控制之下。如果没有,你有更大的问题。

              【讨论】:

                【解决方案8】:

                此外,您可以让 UI 通过某种 PersonBuilder 对象传递构建 Person 对象所需的数据,以便将对象创建合并到域/业务层中,并且您可以将 Person 对象保持在以下状态总是一致的。这对于更复杂的实体更有意义,但是即使对于简单的实体,集中对象创建也是很好的,就像集中持久性等一样。

                【讨论】:

                  猜你喜欢
                  • 2023-03-21
                  • 1970-01-01
                  • 2010-09-13
                  • 2011-07-01
                  • 1970-01-01
                  • 1970-01-01
                  • 2015-07-06
                  • 2015-01-29
                  • 1970-01-01
                  相关资源
                  最近更新 更多