聊聊RESTful - 接口设计篇(三)

REST

(本文为“聊聊RESTful”系列文章第四篇,其他文章可通过篇尾链接查看)

大家好,欢迎大家来到“就浩这口”,我们继续上一篇的话题,接着跟大家聊聊,RESTful的接口设计。今天的话题是异常处理与版本控制。

异常处理

异常处理,是一个架构设计中非常重要,却又常被忽略的话题。在HTTP中,正确处理异常的方式就是用状态码再配合错误码来表示异常。这也同样体现了上一篇中提到的REST的一个架构属性可见性

状态码

异常大体上可以分为两类,4xx客户端错误和5xx服务端错误。每个状态码的具体含义大家可以参看RFC7231,我这里不再做重复的说明了。

The 4xx (Client Error) class of status code indicates that the client seems to have erred. Except when responding to a HEAD request, the server SHOULD send a representation containing an explanation of the error situation, and whether it is a temporary or permanent condition. These status codes are applicable to any request method. User agents SHOULD display any included representation to the user.

The 5xx (Server Error) class of status code indicates that the server is aware that it has erred or is incapable of performing the requested method. Except when responding to a HEAD request, the server SHOULD send a representation containing an explanation of the error situation, and whether it is a temporary or permanent condition. A user agent SHOULD display any included representation to the user. These response codes are applicable to any request method.

实践中我们常犯的错误就是对所有异常都返回5xx,或者说我们是任由程序这么做而没有做出正确的干涉。例如,接口要求客户端请求消息体为Json格式,但客户端没有这么做,或者提交了一个错误的Json格式,这时我们应该响应400 Bad Request或者415 Unsupported Media Type,但实际中,常常因为我们没有做充分的输入校验,导致程序报错,直接返回了500 Internal Server Error。这样的后果是,客户端第一反应是你的服务出了问题,而不是检查自己的错误,一方面让服务端蒙受“不白之冤”,另一方面也影响了客户端的使用体验。所以按照HTTP规范去设计和实现异常处理逻辑,对服务端和客户端都是有好处的。

错误码

虽然HTTP状态码为异常情况作了明确定义,但这也仅仅是一些通用异常。一方面实际项目中肯定会有更多种情况的异常,另一方面那些与业务强相关的异常也无法完全依赖于HTTP状态码。那么就需要我们自定义符合自己实际项目需求的错误码来补充表述异常的具体情况。

正如RFC的规范说明,除了HEAD请求外,服务端应该发送解释异常情况的表述。我在自己的接口中会采用这种格式:

1
2
3
4
5
{
"code": "",
"message": "",
"detail": {}
}

code就是错误码(因为只有在异常情况下,才会返回这个错误表述,响应状态已经充分表明这是一个错误响应,所以这里用code就足够了,没必要用errorCode),是根据我们项目的实际情况定义的错误编码。message是对应code的具体说明,是供开发者阅读的。detail也是对应code的详细信息,但它是结构化的表述信息,是供程序使用的,值被设计为Json对象,其格式也是需要事先定义的。

如何定义错误码并不是一个很难的问题,但也会有不同的风格。我见过的大多数接口错误码都是使用字母数字编号的,但也有一些使用的是英文短语。使用英文短语的好处就像我在上篇文章中善用枚举一段所说的,它能起到见名知意的效果,有助于我们清晰直观地了解异常情况,也能在项目初期督促我们更好的理解项目需求。

不过在有些时候我仍旧建议大家使用字母数字编号的错误码。在项目发展期,难免会出现很多bug,而我们可能并不希望客户端通过错误码就能很清晰的了解服务端出现的问题,这时字母数字编号的错误码就体现出了它的优势,既能在出错时为我们遮羞,又能在报障时辅助我们排查。

错误码设计中也要避免过度设计,尤其是对于那些强迫症完美主义者。首先,错误码的设计更多的是针对具体业务情况的,往往是配合4xx来使用的。而5xx往往都是些无法预测何时会发生的异常,一般HTTP状态码本身就够用了。所以不要把错误码设计成大而全,但又脱离实际需求的样子。其次,就算能够与需求相对应,也未必就一定要设计很多错误码。尤其是对于一些不是很复杂的接口系统,多个错误码可能对客户端根本就没有意义,可能很少量的错误码再配合message就已经够用了。这个原则就是,只有当区别对待错误码对客户端有意义时,才设计对应的错误码。

版本控制

随着业务不断发展,程序也要不断升级,那么如何区分不同版本的接口就成了一个问题。一般而言有三种方式:

  1. 在URI中添加版本信息,例如:/v1/book/9780321125217
  2. 在Content-Type中添加版本信息,例如:Content-Type: application/json;version=1.0
  3. 自定义表示版本的头部,例如:Jhzk-Version: 1.0

三种方法都是非标准的,我用过1和3,总体来讲我认为1是最好的方式,因为实现起来很简单,而且不论对服务端还是客户端都很友好。如果使用了2和3的话,我相信很多人连怎么调用接口都不会了。

那么在REST理论中,版本应该怎么体现呢,Fielding博士给出的建议是,不要版本化,总的来说还是他关于REST必须是超媒体的观点。大家可以看下这篇访谈录“Roy Fielding on Versioning, Hypermedia, and REST”译文)以了解他更具体的观点。这篇访谈录中也讨论了关于超媒体的话题。我只能说理想与现实还是相差甚远的。

今天就跟大家聊到这里了,欢迎大家扫描下方二维码,关注我的公众号“就浩这口”,收听这篇文章的语音版解说,并持续关注后续内容。下一期我将继续为大家介绍接口设计的**,我们下期再见。

传送门

陈浩 wechat
欢迎扫描上面二维码,关注我的公众号“就浩这口”
感谢您的支持,我会创作更多更好的内容