vlambda博客
学习文章列表

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


御剑轩
致力于实践与传播优雅的编码之道
7篇原创内容
Official Account

对于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. 笔者的设计


基于上述原则,笔者对此的新的设计原则如下:

  • 规则1:2XX仅表示业务上成功处理请求

  • 规则2:使用4XX来表示业务错误,4XX中有特别设计的,使用特别设计的,比如权限不足,使用403。而没有特别设计的,则考虑使用400

  • 规则3:对于4XX的响应,再额外使用业务错误码来表示更进一步的业务上的错误含义

  • 规则4:使用字符来表示业务错误描述码。


此API摘自myddd-vertx,基于Vert.x与Kotlin的响应式DDD框架

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


4. 看下别人怎么做的



当然,一个问题不能仅从自身角度出发来思考,要多参阅别人的意见与做法。

国内著名的阮一峰老师在其RESTful API 最佳实践一文中也提及过此点,但并未提及具体原因。


3.2 发生错误时,不要返回 200 状态码


有一种不恰当的做法是,即使发生错误,也返回200状态码,把错误信息放在数据体里面...


再参考一些主流的API的设计,也可以看出其对此点的设计方式


Github Api

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


ZOOM API



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


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