技术随笔:Rest Api设计中处理业务错误的一些思考
对于Rest Api中要如何处理业务错误这个事情,这并不算是一个非常大的问题。事实上,对大多数架构师来说,可能很多人都不会太在意这个点。
但再小的地方也能有更优雅更好的实现方式,刚好最近笔者也遇到并思考过这个问题,特记录下来。
1. http响应码
我们都知道,http响应码是有它的标准含义的,一般而言,笔者建议遵守这个标准,http响应码从1XX到5XX都有其特定的意义,但在Rest Api中,使用最多的可能还是以2XX和4XX为主
# 2XX代表成功
200 OK
201 Created
202 Accepted
203 Non-Authoritative Information
204 No Content
# 4XX代表出现问题了
400 Bad Request
401 Unauthorized
402 Payment Required
403 Forbidden
404 Not Found
405 Method Not Allowed
406 Not Acceptable
具体参阅规范官方文档 Http Status Code
2. 如何响应业务错误
在这之前,笔者也没有特别注意到这个点,统一使用200响应码,再以业务状态码
这种方式结合使用。这是一种常见的方式
2.1 常见的方式
示例如下:
# response 200 业务正常
{
"code": 0,
"msg": "OK",
"data": {
"id": "123"
}
}
# response 200 业务上出错
{
"code":21
"msg": "ID_CAN_NOT_NULL",
}
如上述代码所示,这种做法是一种比较常见的做法,用200表示网络请求成功,而具体到业务是否正常还是异常,再使用业务码来区分。
如上述使用的code值,当为特定值是(如0)表示业务上成功,而其它值则表示不同的业务错误。而成功的响应则放到诸如data字段中。
这种做法是否有合适与优雅?
2.2 笔者的思考
最近在设计一个API时,笔者本来也按旧有的方式,继续按照上述做法来弄,因为以前是这样弄的。但后面仔细想想,就问了下自己:为什么这样,理由是什么?
上述方式的一个优点在于,对于调用方而言,减少对状态码的关注与处理,只处理响应为200的情况就可以了。但除了这个优点,我暂时想不出这种模式有其它优点。
笔者细细想了下,这种模式有几个缺点,也是笔者之所以要改变做法的原因所在:
2.2.1 缺点一:不利于监控或统计等其它场景的扩充
这也是笔者认为最重要的一个缺点
如果项目处于早期,基本上遇不上监控或统计的需求,所以这个点很容易被忽略。但随着项目或产品的使用范围越来越多,自然监控或统计会提上日程,那这种设计就会造成这种场景上的困难。
比如:我们需要统计或监控基于IP或客户的维度,某个API调用了多少次,成功了多少次,失败了多少次。
这样的场景,无论是自己编码实现,还是通过类似一些ELK等工具来分析实现,或是直接从nginx日志中来分析,如果采用上述设计下,都会加大这个工作量,甚至一些场景下无法实现。
如果日志有包含响应体还好,还能通过code来进行统计,要是没有类似的响应体日志,那这个需求就可能实现不了了。
但如果我们不这样设计,而是把200仅设计成为业务成功,那上述需求,无论使用哪种方式,都不会遇到任何阻碍。
2.2.2 缺点二:不够遵循单一职责原则
我们都知道面向对象的基本原则中就有这么个原则:一个类,一个方法或一个模块,只负责一件事。
那以此类推,对于响应码,我们也可以参照这个原则来设计更好。
将200响应表示为业务成功与业务失败的混合,这个明显就让200这个状态码的职责复杂化了,为什么不让它仅表示业务成功呢,这样会不会更纯粹。
而且并不是说只有200这个响应码,我们还有4XX
这个系列可以用,完全可以把业务错误划分到这个类别中去。事实上,我们看下4XX
这个类别,可以明显感知到,它本身就包含了一些业务错误,比如权限不够,被禁止,资源不存在等,这些本身也可以算到业务错误的一部分。
2.3 错误码的思考
上述做法,除了对使用200
来响应业务错误这个点觉得不太合理以外,另外一个笔者的观点就是: 不建议使用数值来表示错误码
一些团队或人可能偏向使用数值来表示错误码,比如101表示XXX上的问题,102表示另一种业务错误。个人不是很建议使用此种做法。
因为数字并不足够表意。使用字符是更合适的做法。
当然,使用数字的好处在于匹配比字符更快。程序识别上会更快。但如果我们不使用上述设计,这个点就无须考虑。
3. 笔者的设计
基于上述原则,笔者对此的新的设计原则如下:
规则1:2XX仅表示业务上成功处理请求
规则2:使用4XX来表示业务错误,4XX中有特别设计的,使用特别设计的,比如权限不足,使用403。而没有特别设计的,则考虑使用
400
规则3:对于4XX的响应,再额外使用
业务错误码
来表示更进一步的业务上的错误含义规则4:使用字符来表示业务错误描述码。
此API摘自myddd-vertx,基于Vert.x与Kotlin的响应式DDD框架
4. 看下别人怎么做的
当然,一个问题不能仅从自身角度出发来思考,要多参阅别人的意见与做法。
国内著名的阮一峰老师在其RESTful API 最佳实践一文中也提及过此点,但并未提及具体原因。
3.2 发生错误时,不要返回 200 状态码
有一种不恰当的做法是,即使发生错误,也返回200状态码,把错误信息放在数据体里面...
再参考一些主流的API的设计,也可以看出其对此点的设计方式
Github Api
ZOOM API
当然,也有不是这样做的,比如instagram的API,它是通过meta字段来区分业务上的正确与错误
你是如何想的?,见仁见智吧