我们使用存储过程在数据库中实现大部分业务规则。
我永远无法决定如何最好地将数据约束违规错误从数据库传递回用户界面。我所谈论的约束更多地与业务规则相关,而不是数据完整性。
例如,诸如“无法插入重复键行”之类的db错误与业务规则“您不能拥有多个具有相同名称的Foo”相同。但是我们已经在最常见的位置“实现”它:作为一个唯一的约束,在违反规则时抛出异常。
其他规则,例如“您每天只允许100个Foos”不会导致错误,因为它们由应用程序代码检查并通过的return empty dataset
等自定义代码优雅处理回到ui层。
其中有瑕疵。我们的ui代码看起来像这样(这是AJAX.NET webservices代码,但任何ajax框架都可以):
WebService.AddFoo("foo", onComplete, onError); // ajax call to web service
function onComplete(newFooId) {
if(!newFooId) {
alert('You reached your max number of Foos for the day')
return
}
// update ui as normal here
}
function onError(e) {
if(e.get_message().indexOf('duplicate key')) {
alert('A Foo with that name already exists');
return;
}
// REAL error handling code here
}
(作为旁注:我注意到这是stackoverflow在您过快提交评论时所执行的操作:服务器生成HTTP 500
响应并且ui捕获它。)
所以你看,我们在这两个地方处理业务规则违规,其中一个(即唯一的constaint错误)作为一个特殊情况被处理为应该处理 real 错误(不违反业务规则),因为.NET将异常一直传播到onError()
处理程序。
这感觉不对。我认为我的选择是:
"select name from Foo where name = @Name"
)并返回应用服务器所期望的“违反业务规则”标志,insert into Foo
,捕获任何异常,并将其转换为应用服务器期望的“业务规则违反”标志insert into Foo
(如3)并让异常传播到ui, plus 让应用服务器将业务规则违规提升为真实Exceptions
(而不是1) )。这样,所有错误都在ui层的onError()
(或类似)代码中处理。我喜欢的2)和3)是业务规则违规被“抛出”实现它们:在存储过程中。我不喜欢的1)和3)是我认为它们涉及像"if error.IndexOf('duplicate key')"
这样的愚蠢的检查,就像当前ui层中的那样。
修改:我喜欢4),但大多数人都说只在特殊情况下使用Exception
。
那么,你们如何优雅地处理传播业务规则违规行为?
答案 0 :(得分:5)
我们不在数据库中执行业务逻辑,但我们确实拥有所有验证服务器端,低级DB CRUD操作与更高级别的业务逻辑和控制器代码分开。
我们在内部尝试做的是使用Validation.addError(message,[fieldname])
等函数传递验证对象。各种应用程序层将验证结果附加到此对象上,然后我们调用Validation.toJson()
以生成如下所示的结果:
{
success:false,
general_message:"You have reached your max number of Foos for the day",
errors:{
last_name:"This field is required",
mrn:"Either SSN or MRN must be entered",
zipcode:"996852 is not in Bernalillo county. Only Bernalillo residents are eligible"
}
}
这可以很容易地在客户端处理,以显示与各个字段以及常规消息相关的消息。
关于约束违规,我们使用#2,即我们在插入/更新之前检查潜在的违规,并将错误附加到验证对象。
答案 1 :(得分:3)
问题实际上是系统架构的限制之一。通过将所有逻辑推入数据库,您需要在两个位置处理它(而不是构建一个将UI与数据库链接起来的业务逻辑层。然后,只要您拥有一层业务逻辑,就会失去所有在存储过程中使用逻辑的好处。不提倡一个或另一个。两者都是平等的。或者不要吮吸。取决于你如何看待它。
我在哪儿? 对。
我认为2和3的组合可能是要走的路。
通过预先排除错误,您可以创建一组可以从面向UI的代码调用的过程,以向用户提供详细的特定于实现的反馈。你不一定需要在逐个字段的基础上使用ajax来做这件事,但你可以。
数据库中的唯一约束和其他规则然后成为所有数据的最终健全性检查,并且可以假设数据在发送之前是好的,并且当然抛出异常(前提是这些应始终使用有效数据调用过程,因此无效数据是特殊情况。)
答案 2 :(得分:2)
为了防御#4,SQL Server预定义了一个非常有序的错误严重性级别层次结构。因为当你指出处理逻辑错误时,我会倾向于按照SP和UI抽象之间的惯例来处理这个问题,而不是添加一堆额外的耦合。特别是因为您可以使用值和字符串引发错误。
答案 3 :(得分:1)
我看到很多基于Ajax的应用程序在用户离开编辑框时对用户名(如果它已经存在)等字段进行实时检查。在我看来,比基于数据库约束提出异常更好的方法 - 它更主动,因为你有一个真正的进程:获取值,检查它是否有效,如果没有显示错误,如果没有错误,允许继续。所以看来选项2是一个很好的选择。
答案 4 :(得分:1)
存储过程可以使用RAISERROR语句将错误信息返回给调用者。这可以以允许用户界面决定错误出现方式的方式使用,同时允许存储过程提供错误的详细信息。
可以使用 msg_id ,严重性和状态以及一组错误参数调用RAISERROR。以这种方式使用时,必须使用sp_addmessage系统存储过程将具有给定 msg_id 的消息输入到数据库中。可以将此 msg_id 检索为SqlException中的ErrorNumber属性,该属性将在调用存储过程的.NET代码中引发。然后,用户界面可以决定要显示的消息类型或其他指示。
将错误参数替换为生成的错误消息,类似于 printf 语句在C中的工作方式。但是,如果您只想将参数传递回UI以便UI可以决定如何使用它们,只是使错误消息没有文本,只是占位符的参数。一条消息可能是“%s”|%d'传回一个字符串参数(在引号中)和一个数字参数。 .NET代码可以拆分它们并在用户界面中使用它们,无论你喜欢什么。
RAISERROR也可以用在存储过程中的TRY CATCH块中。这将允许您捕获重复键错误,并将其替换为您自己的错误号,这意味着“插入时重复键”代码,并且它可以包含实际键值。您的UI可以使用它来显示“订单号已存在”,其中“x”是提供的键值。
答案 5 :(得分:1)
这就是我做事的方式,虽然它可能不适合你:
我通常会选择先发制人模式,但这很大程度上取决于您的应用程序架构。
对我来说(在我的环境中)检查中间(业务对象)层中的大多数错误是有意义的。这是所有其他特定于业务的逻辑发生的地方,所以我也尝试在这里保留其余的逻辑。我认为数据库是一个可以保留我的对象的地方。
在验证方面,最简单的错误可能会被困在javascript(格式,字段长度等)中,当然你永远不会认为发生了那些错误检查。这些错误也会在更安全,更受控制的服务器端代码世界中得到检查。
业务规则(例如“您每天只能拥有这么多元素”)将在业务对象层的服务器端代码中进行检查。
仅在数据库中检查数据规则(参照完整性,唯一字段约束等)。我们也抢先检查中间层中的所有这些内容,以避免不必要地访问数据库。
因此,我的数据库只能保护自己免受它能够处理的简单,以数据为中心的规则;更多变量,面向商业的规则存在于对象的土地上,而不是记录的土地上。