写点什么

技术随笔:Rest Api 设计中处理业务错误的一些思考

用户头像
御剑
关注
发布于: 2021 年 02 月 22 日

对于 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 的设计,也可以看出其对此点的设计方式

Github Api



ZOOM API



当然,也有不是这样做的,比如 instagram 的 API,它是通过 meta 字段来区分业务上的正确与错误



你是如何想的?,见仁见智吧


---------------

更多优质文章,请访问笔者的个人网站 https://lingenliu.cc 或关公众号:【御剑轩】 - 致力于实践与传播优雅的编码之道




发布于: 2021 年 02 月 22 日阅读数: 58
用户头像

御剑

关注

WHATEVER IT TAKES 2018.07.15 加入

全栈式技术开发

评论

发布
暂无评论
技术随笔:Rest Api设计中处理业务错误的一些思考