对于Rest Api中要如何处理业务错误这个事情,这并不算是一个非常大的问题。事实上,对大多数架构师来说,可能很多人都不会太在意这个点。
但再小的地方也能有更优雅更好的实现方式,刚好最近笔者也遇到并思考过这个问题,特记录下来。
1. http响应码
我们都知道,http响应码是有它的标准含义的,一般而言,笔者建议遵守这个标准,http响应码从1XX到5XX都有其特定的意义,但在Rest Api中,使用最多的可能还是以2XX和4XX为主
# 2XX代表成功
200 OK [RFC7231, Section 6.3.1]
201 Created [RFC7231, Section 6.3.2]
202 Accepted [RFC7231, Section 6.3.3]
203 Non-Authoritative Information [RFC7231, Section 6.3.4]
204 No Content [RFC7231, Section 6.3.5]
# 4XX代表出现问题了
400 Bad Request [RFC7231, Section 6.5.1]
401 Unauthorized [RFC7235, Section 3.1]
402 Payment Required [RFC7231, Section 6.5.2]
403 Forbidden [RFC7231, Section 6.5.3]
404 Not Found [RFC7231, Section 6.5.4]
405 Method Not Allowed [RFC7231, Section 6.5.5]
406 Not Acceptable [RFC7231, Section 6.5.6]
具体参阅规范官方文档 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. 笔者的设计
基于上述原则,笔者对此的新的设计原则如下:
- 原则一:2XX仅表示业务上成功处理请求
- 原则二:使用4XX来表示业务错误,4XX中有特别设计的,使用特别设计的,比如权限不足,使用403。而没有特别设计的,则统一使用
400
- 原则三:对于4XX的响应,再额外使用
业务错误码
来表示更进一步的业务上的错误含义 - 原则四:使用字符来表示业务错误描述码。
> 此API摘自myddd-vertx,基于Vert.x与Kotlin的响应式DDD框架
主流API的参考
当然,一个问题不能仅从自身角度出发来思考,要多参阅别人的意见与做法。
国内著名的阮一峰老师在其RESTful API 最佳实践也提及过此点,但并未提及具体原因。
3.2 发生错误时,不要返回 200 状态码<br>
有一种不恰当的做法是,即使发生错误,也返回200状态码,把错误信息放在数据体里面...
再参考一些主流的API的设计,也可以看出其对此点的设计方式
> ZOOM API
当然,也有不是这样做的,比如instagram的API,它是通过meta字段来区分业务上的正确与错误
你是如何想的?,见仁见智吧
更多优质文章,请访问笔者的个人网站 https://lingenliu.cc 或关公众号:【御剑轩】 - 致力于实践与传播优雅的编码之道