你在哪里进行验证?

时间:2009-06-01 19:18:32

标签: validation architecture

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

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

  • 前端UI层,可以是asp.net webform,也可以是窗口(用于编辑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 = Success,Non Zero等于失败,数字表示失败的原因。

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

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

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

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

  • 也许是另一个

为长篇大论的问题道歉,但我肯定想听听你对此的看法。

谢谢!

8 个答案:

答案 0 :(得分:7)

多层验证适用于多层应用。

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

然而,业务逻辑应该具有最大的验证责任份额......并且如果这是“重复”的话,一旦这不是问题,即,如果业务层重新检查应该已经在UI - BL应该检查所有业务规则(这对UI的正确性进行了双重检查,启用了多个不同的UI客户端,这些客户端可能并非在检查中都是完美的 - 例如智能手机上可能没有良好javascript的特殊客户端,等等 - 并且,有点,防范恶意攻击的客户端。)

当业务逻辑将“已验证”的数据保存到数据库时, 层应该执行自己的检查 - 数据库很擅长这一点,并且再次不用担心重复 - 执行数据完整性是数据库的工作(您可能希望有一天向其提供数据的不同方式,例如从其他来源导入大量人员的“批量加载程序”,确保所有这些方法的关键是关键加载数据总是尊重数据完整性规则);一些规则,例如唯一性和参照完整性,最好在数据库中实施,特别是出于性能原因。

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

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

答案 1 :(得分:4)

只是一个快速(并希望有帮助)的评论:当你想知道在何处放置验证时,请尝试假装,很快,您将使用您尚未熟悉的技术完全重新创建UI层**。尽量避免使用任何类似验证的业务逻辑,确保您必须在新技术中重写。

您会发现异常 - 业务逻辑最终会出现在您的UI层中,但它仍然是一个有用的考虑因素。

**移动开发,Silverlight,语音XML,无论如何 - 假装您不了解“新”UI层的技术,可以帮助您抽象出您的疑虑,减少对实施细节的厌倦。

答案 2 :(得分:3)

唯一重要的一点是:

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

答案 3 :(得分:2)

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

当我在一个项目中时,我假设我正在制作一个框架,大部分时间都不是这样。每个图层都是独立的,必须在执行操作之前检查所有图层输入

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

您永远不想让坏数据进入数据库。因此,您永远不能相信您从业务层获得的数据。需要检查。

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

答案 4 :(得分:2)

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

答案 5 :(得分:1)

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

答案 6 :(得分:1)

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

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

答案 7 :(得分:1)

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