我总是倾向于通过服务层“保护”我的持久层免受违规。但是,我开始怀疑它是否真的有必要。花时间使我的数据库健壮,建立关系和时间有什么意义呢?数据完整性从未实际发挥作用。
例如,考虑在User
字段上具有唯一约束的Email
表。我自然希望在我的服务层中编写阻止代码,以确保在尝试添加任何内容之前,添加的电子邮件尚未存在于数据库中。在过去,我从来没有真正看到它有任何问题,因为我已经接触过更多&更多最佳实践/设计原则我觉得这种方法不是很DRY。
那么,始终确保进入持久层的数据确实“有效”或让无效数据到达数据库并处理错误更自然是正确的吗?
答案 0 :(得分:2)
请不要那样做。
在并发环境中实现甚至“简单”约束(例如密钥)显然是非平凡的。例如,仅在一步中查询数据库并且仅在第一步返回空结果时才允许在另一步中插入 - 如果并发事务插入了您尝试插入(并已提交)的相同值,该怎么办在你的第一步和第二步之间?您有竞争条件可能导致重复数据。对此最简单的解决方案可能是拥有一个全局锁定来序列化事务,但随后可伸缩性就会消失......
对于键的INSERT / UPDATE / DELETE操作的其他组合以及其他类型的约束(例如外键,甚至在某些情况下甚至是CHECK)也存在类似的考虑。
DBMS在过去几十年中设计了非常聪明的方法,在这些情况下既正确又高效,又允许您以声明性方式轻松定义约束,最大限度地减少错误的机会。访问同一数据库的所有应用程序都将自动受益于这些集中约束。
如果您绝对必须选择哪个代码层不应验证数据,那么数据库应该是 last 选项。
那么,始终确保进入持久层的数据确实是“有效”(服务层)或让无效数据到达数据库并处理错误更自然是正确的吗?
永远不要假设正确的数据,并且始终尽可能在数据库级别进行验证。
是否还在代码的上层验证取决于具体情况,但在密钥违规的情况下,我会让数据库完成繁重的工作。
答案 1 :(得分:1)
这样做有两个原因。可以从另一个应用程序访问数据库..
您可能会在代码中出现错误,并将数据放入数据库中,这是因为您的服务层假设这种情况永远不会发生,如果运气不好就会导致数据丢失,无声数据损坏是最糟糕的情况。
我总是把数据库中的规则看作是我在代码中犯错的极为罕见的场合的支持。 :)
要记住的是,如果你需要,你可以随时放松约束,在用户花费大量精力输入数据之后收紧它们将会更加困难。
要谨慎对待这个词,在IT方面,它意味着比你希望的更快。
答案 2 :(得分:1)
即使没有确凿的答案,我认为这是一个很好的问题。
首先,我非常支持在数据库中至少包含基本验证,并让数据库完成它所擅长的工作。至少,这意味着外键,NOT NULL
在适当的情况下,尽可能使用强类型字段(例如,不要将整数所属的文本字段放置),唯一约束等。让数据库处理并发性也是最重要的(正如@Branko Dimitrijevic指出的那样)并且事务原子性应归数据库所有。
如果这是多余的,那么就是这样。验证太多验证而不是太少。
但是,我认为即使逻辑存在于数据库中,业务层也应该知道它正在执行的验证。
区分异常和验证错误可能更容易。在大多数语言中,失败的数据操作可能会表现为某种异常。大多数人(包括我在内)认为使用常规程序流程的异常是不好的,我认为电子邮件验证失败(例如)不是“例外”的情况。
将其置于更荒谬的层面,想象一下,只是为了确定用户是否填写了表单上的所有必填字段而点击数据库。
换句话说,我宁愿调用方法IsEmailValid()
并接收一个布尔值而不是尝试必须确定抛出的数据库错误是否意味着该电子邮件已被其他人使用。
这种方法也可能表现更好,并避免因为INSERT失败而导致跳过ID等烦恼(从SQL Server的角度讲)。
验证电子邮件的逻辑很可能存在于可重用的存储过程中,如果它比简单的唯一约束更复杂。
最终,这个简单的唯一约束可以在业务层出错时提供最终保护。
有些验证根本不需要让数据库调用成功,即使数据库可以轻松处理它。
有些验证比单独使用数据库构造/函数表达的要复杂得多。
跨应用程序的业务规则可能会因相同(完全有效)的数据而有所不同。
某些验证非常重要或昂贵,应该在数据访问之前进行验证。
一些简单的约束,如字段类型/长度可以自动化(任何通过ORM运行的东西可能都有一定程度的自动化)。