前两篇:

(一)前言&RPC

(二)SOAP

(三)REST(now)

接下来,在RPC和SOAP之后,我们迎来了REST。

REST(Representational State Transfer),实际上一般指的是RESTful的架构风格,是由Roy Fielding在2000年的这篇著名论文里提出的。

那REST到底对API设计做了什么改进呢?先来回顾一下前面两篇文章里介绍的RPC和SOAP,RPC类似于系统和系统之间讲黑话,SOAP改进了RPC,使之有了统一“普通话”。但是“普通话”是不是最优解呢?我们来看一个调用远程系统创建用户时可能遇到的接口名:

createUser() //很平常的命名

insertUser() //也可以接受

chuangjianyonghu() //别说你没看到过

chuangjianUser() //土洋结合

cjyh() //中文拼音首字母缩写

api123321() //恩,保密工作做的不错?

根本就有无穷种可能性好伐?那么,我们能不能有一个接口的规范?比如,对于任何系统,暴露出来的创建用户一定是createUser?REST以一种革命性的抽象,巧妙的解决了这个问题。

SOAP虽然工作在HTTP之上,但实际上并没有好好的利用HTTP,所有的内容都在request和response的body里面。那如何好好利用HTTP协议本身呢?

答案就是:面向资源(resource

加上利用HTTP协议本身。包括协议中的8种方法:OPTIONS,GET,HEAD,POST,PUT,DELETE, TRACE,CONNECT,REST, 以及HTTP response status codes:2xx,4xx,5xx,HTTP Header的定义,HTTP 是无状态的等特性。

其中最最关键的抽象是把系统的交互看成一个对有名字的资源的访问

比如上面用户相关的REST接口:

{GET}     /users            //获取用户列表,返回User[]
{POST}    /users            //创建用户,返回id
{GET}     /users/1          //获取id为1的用户信息,返回User
{PUT}     /users/1          //更新id为1的用户信息,返回User
{DELETE}  /users/1          //删除id为1的用户信息,无返回或者返回是否命中
{GET}     /users?name=enix  //搜索名字为enix的用户列表,返回User[]
......

你看,他即不是黑话,也不是自由度很高的普通话,而是在现有的、已经被广泛使用的一种通信规范(HTTP)的基础上来设计接口。用一个URI(Uniform Resource Identifiers),上面例子里面的/users来代表user这个资源,所有的接口都利用HTTP的特性在这个URI上做文章。如果你遵守REST的规范来设计接口的话,所有人设计出来的API基本都是一样的。

网上介绍REST的文章太多了,这里只提一点,不要设计出RPC风格的伪REST接口。下面是两个例子。


给用户发送消息的接口,很多系统会设计成这样:

{POST}   /sendSMSMessageToUser
body:{"userid":1,"message":"hello there"}

在思想上,这还是个面向方法的RPC接口,我们需要转换思想,面向资源

正确的REST style的方法是这样的:

{POST}   /users/1/message
body:{"message":"hello there"}

虽然这两种方式看起来很像,但是我们的关注点从sendXXXX这个方法,转移到了user的message这个resource上面了。(另外,后面的例子里面把sms也去掉了,因为这是server端的问题,client端只需要表达需要给用户发送消息就行了,至于是email还是sms,只是server端的实现细节罢了)


另外一个经典的伪REST接口类型:

{POST} /users/login
body:{"username":"enix","password":123456}

恩,这是一个很普通的用户登录接口,但是很多系统里面,这样的登录接口,不管成功失败,返回的HTTP status code统一都是200!!!

HTTP status 200 body:{success:true,data:{...}}     //登录成功
HTTP status 200 body:{success:false,data:{...}}    //登录失败

上面响应技术上讲叫做envelope(还记得SOAP的xml里面的SOAP-ENV:Envelope吗?),就是把所有结果包在一个信封里面,信封外面写着远程调用的结果,一般是成功或者失败,实际响应内容放到子一级的data里。所以这还是一个RPC style的接口!

而正确的接口应该这么返回:

HTTP status 200 body:{token:"..."}    //登录成功
HTTP status 403 body:{message:"..."}  //登录失败

Remember, HTTP status code is your envelope in REST!!!


REST的优点

Uniform Interface:这个前面已经提到过了,系统之间交互,有一个可预期的接口实在是太重要了。

Stateless:曾几何时,系统扩容绕不开的一个话题就是session同步。HTTP请求本身就是无状态的,怎么保持用户的登录状态呢?大部分系统使用了session机制,也就是浏览器里面记录一个sessionid,对应服务器里面的一块内存,内存里面记录了用户的信息。这个机制在单服务器的时候工作完美,但是当服务器需要扩展性能时,比方说前面加了个负载均衡,后面变成了两台服务器时,就会带来一个问题:如何在保持session同步?要么两个服务器内存同步要么存到第三方(memcache或者redis),比如下面这个典型的配置: 而REST接口是无状态的,依赖header里面token,每次都实时解析(JWT了解一下),虽然略微牺牲了一点性能(其实绝大部分情况只是把header里面的“token” decode&verify一下,不会牵涉到数据库读取,相比去memcache里面同步session,未必性能更差),但是在扩展性能的时候实在太方便了!性能不够?实时顶几台服务器上去。性能过剩?随便下线几台服务器也不在话下。这是热插拔,少了启动时要去cache同步所有session的这个预热过程。

Cache:过去,你可能需要在应用级别(上图的每个instance)里自己实现cache逻辑。如果使用REST的话,cache的相关处理就能提到更高级的地方(load balancing)来做了。为什么呢?因为所有的资源访问都暴露在HTTP层了。比如前面那个例子里的/users{GET} /users/:id这个URL是可以被cache的,只有当有人去POST或者PUTDELETE过这个URL时,才需要刷新缓存。更妙的是,应用层根本不需要再去关心缓存了,不需要到处写@Cacheable("users")了,完全可以交给前面的一层去做!


REST的缺点

健谈:和SOAP的罗嗦(报文体积大)不同,REST接口倾向于健谈(发送请求多)。这其实和现在的前后端划分有关系。以前,大家都利用jsp asp php等类似的技术,在服务端组织数据、生成页面,然后返回给浏览器。现在,因为不止有web端,我们可能还有手机app端、微信端、桌面端,给每个程序开发一套接口是很不经济的。统一接口使用REST的代价就是请求变多了。不再是后端算好所有东西给前端了,前端也有了自己的框架(angular react)和自己的逻辑,后端只是提供数据,也就是资源给前端了。以前一个HTTP请求就能搞定的事情,现在可能要发好多个。特别是在HTTP/2还没有完全普及的现在,浏览器同时发出的请求数量是有限制的(HTTP/1.1可以看这里的表格)。

TMI:Too Many Information或者说Overfetching。比如说我有一个博客的首页,我需要load一个post的列表,我会发一个简单的list请求

{GET}     /posts

返回的结果可能是

HTTP status 200
body:[
  {
      "title" : "hello",
      "author" : {...},
      "description" : "world",
      ...   //以及其他20个字段
  },
  ...
]

作为一个blog的首页,我可能只需要titleauthordescription这三个字段就行了,另外20个字段我完全不关心啊。当然,你可以用sparse fieldsets或者类似的技术来避免这个问题。然而这个问题是REST面向资源的设计的nature所带来的,很难彻底避免(GraphQL对REST的改进之一就是为了解决这个痛点)。


扩展阅读:

REST论文的主要作者在2017年的Reflections on the REST Architectural Style and “Principled Design of the Modern Web Architecture”

一个哥们写了一篇黑REST的文章REST is the new SOAP

然后被另外一个哥们的一篇A Response to REST is the new SOAP驳得体无完肤,还是很有趣的。


下一篇预告:GraphQL