希望您能在下面的场景中看到我所描述的问题。如果不清楚,请告诉我。
你有一个分为三层的应用程序,
在我的前端,我想创建一个新的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及其基础属性。此“附加”属性可能包含验证信息
在此处插入您的想法或模式
也许是另一个
为长篇大论的问题道歉,但我肯定想听听你对此的看法。
谢谢!
答案 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)
唯一重要的一点是:
答案 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对象保持在以下状态:总是一致的。这对于更复杂的实体更有意义,但即使对于简单实体,也可以集中对象创建,就像集中持久性等一样。