关于REST API的为什么

来源:互联网 发布:微信整蛊软件 编辑:程序博客网 时间:2024/05/16 01:03

为什么RESTful API的设计中总是有各种争论不休?

一旦开始REST API的设计,往往关于标准的仁者见仁智者见智的争论和对于规范是接受还是自创的纠结的过程就开始了。说到底,是因为REST本身不是标准也不是规范,更不是协议,仅仅是些概念和几条原则而已。更恼火的是REST是典型的”经常被提起,多数是误读”,所谓简化接口调用其实不是REST的重点,REST更不是只适合CRUDREST的使命不是代替RPC,甚至做为API来说REST有时都不是什么方便的技术。

那为什么要RESTful? REST的好处在于建立一个强大、灵活、可扩展的互联的软件体系结构,如果不相信这个体系结构的强大、灵活和扩展性,只要看一下因特网,看一下HTTP协议在建立这个巨大软件系统中起到的作用就有感觉了。

RESTHTTP究竟是什么关系?

了解一下REST的提出者Roy Fielding,他是如今统治互联网的HTTP协议的专家组负责人,在他那篇重要论文中提出的分布式超媒体系统架构风格和约束从某种角度上说就是在教你使用HTTP的正确姿势,而HTTP本身就是该架构风格的一个典型范例。所以不要再纠结REST API是不是深度依赖HTTP规范了,如果不依赖HTTP规范,你为什么要用REST?

常见的一个争论就是对HTTP状态返回码的使用。 现在国内经常看到一些号称RESTful开发接口的API只定义POST一种方法和200OK一种返回码,所有正确和错误的返回信息都放在封装的报文里,我认为产生这种现象这有几方面原因:

一、早期设计为考虑客户端兼容性定义了封装的出错码(包括也用POST封装PUTDELETE);

二、早期大家对REST的理解比较潜,只是把原来已有RPC接口稍做了些修改就认为实现了REST

三、沿用到现在客户端习惯于这种类RPC的响应处理,认为再去学习HTTP增加了负担。

看一下国外的情况,关于RESTAPI RPC的争论有很多,各种竞争性的解决方案也层出不穷,但象这样做伪RESTful API的还是很见的。不使用HTTP状态返回码,对于互联系统架构有很大的危害性,因为这冒用了REST架构却隐藏了REST语义。使用HTTP状态码不只是规范问题,有很多的实用价值。 比如: 对于系统的监控分析中,很常见的是对access log的监控, 通过Log分析工具可以很轻松的汇总各种操作,如统计某资源的更新操作时,200是修改,201是新建,409是冲突,用现成工具如ELK可以直接出分析结果。同样,40x/50x的相关状态码更是监控系统格外关注的信息。如果一率用200,就屏蔽了大量重要信息。在云服务环境下,这格外重要,因为出错信息首先是给系统看的,然后才是给人分析log的。再举一个例子。有一次做一个WebAPI服务的性能测试,一轮测试跑下来,报表叫一个漂亮,几乎没有请求失败,再细看Responserlog才发现,其实从第一个请求开始所有请求都已经失败,但因为所有API只返回200 OK返回码,测试端以为都是成功的。于是,不得不对Response Body中的JSON内容进行解析,提取特定的code,才能对成功与否进行判断,这大大增加了测试端的计算复杂度和资源消耗。事实上,请求的响应也没人能保证一定是200 OK,严谨的说,客户端一样要处理4xx/5xx的问题。相当于要有两套错误处理的代码。如果偷个懒把HTTP出错码的处理都交给框架统一处理,用户交互又往往很不友好。

现在的客户端框架和兼容性早已不是几年前的水平,对HTTP状态码的处理也不难,设计REST API一定要对常用的HTTP状态码有个比较全面的了解。

REST风格的设计该如何体现REST原则呢?

REST其实原则主要就是几条:全局标识资源、资源互联、无状态、标准方法、多重表述等。

举几个例子说明一下:

1、关于使用/accounts/:x/resources,还是使用/resources/?account=:x

两种方式似乎只是路由上的表示,对代码来说实际没有什么大的不同,为什么应该使用前者? 

RESTURI定义资源,理论上问号后面的部分不是资源URI。要有效利用HTTP的协议建立的系统架构,在大规模分布式访问时充分利用基于HTTP协议各种设施,如cacheproxygatewayCDN,都可以在URI上定义路由控制,比如将某一部分account相关的请求cache在一个服务器上。如果用后者,使用参数方式,虽然cache server也记录url上的参数部分,但/resources/?account=x&page=1/resources/?page=1&account=x会是两个url

而且,/resoucrces/accounts/:x/resources是两个不同业务概念的集合,如果认为/resources已经可以完成所有对resource资源的操作不必多提供一个接口,按这样的逻辑,所有API最后只能沦为数据库表的访问CRUD接口了。

2、关于该如何定义login接口POST/login?API是很不RESTful的接口,为什么呢?

一、REST要求无状态, 但login这个词本身的含义就是个有状态的:如果login,就该有个session吧,还有一个logout的方法来结束session

二、REST要求用标准方法,而login一定是不符合的。

RESTful的接口是什么样的呢?下面这个例子是RESTful的:

POST /tokens

首先,不使用login这个动词,而是按"标准方法"原则使用HTTPPOST动词。同时,相应的资源是token,用复数名词,代表一种实体化的credential资源集合,POST表示在集合中创建一个token,客户端获得token就获得了访问服务的相应授权。可以通过GET重新读取token,也可以DELETE token回收授权,甚至通过POST/PUT更新和延长授权。

session也可以是一种资源,为什么资源定义不用/sessions呢?因为token作为credential是无状态的,而session的实现是有状态的。

3. 关于是否在URL里包含版本号

一种常见的做法是在api的根下加版本号,如:/api/v1/resources/:id。虽然严格说/api/v1/不应该作为资源标识的一部分,这样的表示也无伤大雅。

不过有更推荐的做法。按"多重表述"原则,即便是不同版本的实现,也可以认为是同一资源的不同表现形式,应用采用相同的URI,推荐做法是在HTTP请求头信息中用Accept字段进行区分,比如:

Header"Accept":"vnd.api+json; version=1.1"

这里的vnd.api+jsonJSON API定义的MIME类型,如果是IANA认证的其他组织也可以使用自己的VendorTree,比如,github使用的格式与上面不同:

Header"Accept":"application/vnd.github.v3+json"

当然,话说回来,REST API更多用来定义公共API,如果没有什么历史包袱,对于新系统的REST API设计,应该能够更RESTful一些。如果是小规模的内部服务之间互相调用的接口就不必过于拘泥于细节了,而事实上有时RPC可能更好的选择。

我觉得应该象对待UI一样对待API,重视公共UI上的逻辑性、易用性,不受限于后台技术实现,而是面向使用者,能让使用者不看手册也会用,而且不用错,这是良好的UI体验,也是对良好的API接口的要求。但内部服务的交互就如同向开发者提供的内部UICLI,简陋一点只要大家接受也不至于贻笑大方啦。