1

我总是倾向于通过服务层“保护”我的持久层免受侵犯。但是,我开始怀疑它是否真的有必要。花时间使我的数据库健壮,建立关系和数据完整性,但实际上它从未真正发挥作用,这有什么意义呢?

例如,考虑一个User在字段上具有唯一约束的表Email。我自然会想在我的服务层中编写拦截器代码,以确保在尝试添加任何内容之前,正在添加的电子邮件尚未在数据库中。在过去,我从未真正看到它有什么问题,但是,随着我接触到越来越多的最佳实践/设计原则,我觉得这种方法不是很

那么,始终确保进入持久层的数据确实“有效”是正确的,还是让无效数据进入数据库并处理错误更自然?

4

3 回答 3

2

请不要那样做。

在并发环境中,实现甚至“简单”的约束(例如键)显然不是微不足道的。例如,仅在第一步返回空结果时才在一个步骤中查询数据库并允许在另一个步骤中插入是不够的 - 如果并发事务插入了您尝试插入(并提交)的相同值怎么第一步和第二步?您有可能导致重复数据的竞争条件。可能最简单的解决方案是使用全局锁来序列化事务,但随后可伸缩性就消失了……

对于键上的其他 INSERT / UPDATE / DELETE 操作组合以及其他类型的约束(例如外键甚至在某些情况下的 CHECK)也存在类似的考虑。

几十年来, DBMS 设计了非常聪明的方法,在此类情况下既正确又高效,同时允许您以声明性方式轻松定义约束,最大限度地减少出错的机会。所有访问同一数据库的应用程序都将自动受益于这些集中式约束。

如果您绝对必须选择哪一层代码不应该验证数据,那么数据库应该是您的最后选择。

那么,始终确保进入持久层的数据确实“有效”(服务层)是正确的,还是让无效数据进入数据库并处理错误更自然?

永远不要假设正确的数据,并且尽可能在数据库级别进行验证。

是否还要在上层代码中进行验证取决于具体情况,但在密钥违规的情况下,我会让数据库完成繁重的工作。

于 2012-09-10T22:31:22.807 回答
1

这样做的两个原因。可以从另一个应用程序访问数据库..

您可能在代码中犯了一个小错误,并将数据放入数据库中,因为您的服务层在假设这永远不会发生的情况下运行,如果您幸运的话,它会崩溃,最坏的情况是无声数据损坏。

当我在代码中出错时,我一直将数据库中的规则视为异常罕见的情况的支持。:)

要记住的是,如果您需要,您可以随时放松约束,在用户花费大量精力输入数据后收紧它们会带来更大的问题。

要真正警惕这个词,在 IT 中,它意味着比你希望的要早得多。

于 2012-09-10T22:16:21.843 回答
1

尽管没有确定的答案,但我认为这是一个很好的问题。

首先,我非常支持在数据库中至少包含基本的验证,并让数据库做它擅长的事情。至少,这意味着外键,NOT NULL在适当的情况下,尽可能强类型字段(例如,不要将文本字段放在整数所属的位置),唯一约束等。让数据库处理并发也是最重要的(正如@Branko Dimitrijevic 指出的那样out) 并且事务原子性应该归数据库所有。

如果这是适度多余的,那就这样吧。太多的验证总比太少的好。

但是,我认为即使逻辑存在于数据库中,业务层也应该知道它正在执行的验证。

  • 区分异常和验证错误可能更容易。在大多数语言中,失败的数据操作可能会表现为某种异常。大多数人(包括我)都认为在常规程序流程中使用异常是不好的,我认为电子邮件验证失败(例如)不是“异常”情况。

    把它带到一个更荒谬的水平,想象一下访问数据库只是为了确定用户是否填写了表单上的所有必填字段。

    换句话说,我宁愿调用一个方法IsEmailValid()并接收一个布尔值,也不愿尝试确定抛出的数据库错误是否意味着该电子邮件已被其他人使用。

    这种方法也可以执行得更好,并避免像跳过 ID 这样的烦恼,因为 INSERT 失败(从 SQL Server 的角度来看)。

    如果验证电子邮件的逻辑比简单的唯一约束更复杂,那么它很可能存在于可重用的存储过程中。

    最终,这个简单的唯一约束提供了最终保护,以防业务层出错。

  • 某些验证根本不需要进行数据库调用即可成功,即使数据库可以轻松处理它。

  • 一些验证比单独使用数据库构造/函数更复杂。

  • 即使针对相同(完全有效)的数据,跨应用程序的业务规则也可能不同。

  • 某些验证是如此重要或昂贵,以至于它应该在数据访问之前进行。

  • 一些简单的约束,如字段类型/长度可以自动化(通过 ORM 运行的任何东西都可能具有一定程度的自动化)。

于 2012-09-11T00:48:09.460 回答