对于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 或关公众号:【御剑轩】 - 致力于实践与传播优雅的编码之道

05-18 10:29