目录
REST API是什么?
No.1 数据跟界面解耦合
No.2 无状态
No.3 可缓存
No.4 统一接口/标准化接口
No.5 分层次系统风格
No.6 按需编码(可选)
REST API是什么?
REST中文意思是REpresentational(代表性的)State(状态)Transfer(传输),比较拗口,这个概念是Roy fielding提出的一种应用在分布式系统的架构风格。
很多时候不小心写错了,我们会看到Restful API,碰到这样写法,严格说是错误的。
简单理解,RESTful API就是尽量满足以下六个设计原则来进行实现的接口,它们是:
- 数据跟界面解耦
- 无状态
- 可缓存
- 统一接口/标准化接口
- 分层次系统风格
- 按需编码(可选)
这么多个原则,换个简化的说法,即是,更清晰更标准化更易扩展的接口。
还是很抽象的感觉?
下面举几个例子一一阐述这些原则。
No.1 数据跟界面解耦合
这个很好理解,接口要朝着提供视图需要的数据的方向来设计,定制。
比如我们在做查询用户接口数据/api/user/001的响应时,应该仅仅返回一个用户的json数据
{
"name":"levin",
"photo":"baidu.com/ll/png234u1o343.png"
}
视图代码如下(以web页面为例子,界面代码通常是一些标签包围起来的代码块)
<div>
<div>Name:<span>{{name}}</span></div>
<div>Photo:<img src="{{photo}}"/></div>
</div>
而不是设计一个接口直接生成(当然这个原则并不能阻止部分程序员直接生成带数据的视图,最好不要这样做,让数据和界面分开可以有更高复用性)
<div>
<div>Name:<span>levin</div>
<div>Photo:<img src="baidu.com/ll/png234u1o343.png"/></div>
</div>
No.2 无状态
首先,无状态就是请求这个接口的时候,处理该请求的服务在未获取数据时,该服务是没有地方存储待获取的数据的。
举个例子,还是/api/user/001 这个接口,背后的伪代码可以如下:
let dataService = new DistributedDataService()
@Get("/api/user/{id}")
function getUser(id){
return dataService.queryUser(id)
}
这个接口的实现调用了dataService去查询用户编号是001的信息。
服务本身是没有存储任何状态的,数据可以从数据库或者缓存中获取,这就是保证服务的无状态。
什么情况是有状态呢?
举个例子,伪代码如下:
//local data cache
let dataService = LocalDataService()
@Get("/api/user/{id}")
function getUser(id){
return dataService.queryUser(id)
}
@Delete("/api/user/{id}")
function getUser(id){
return dataService.deleteUser(id)
}
这里使用的dataService是LocalDataService(本地的数据服务),通常单进程运行是没有任何问题的。因为无论如何操作,只要LocalDataService支持线程安全(这个需要开新的一篇来讲),它的状态严格上说不会有问题。
但在分布式系统中,普遍做法是部署多个服务器多实例,而且他们同时提供/api/user/001接口的服务。
这时候问题就来了,每个服务进程都拥有一个localDataService。
当调用来DELETE /api/user/001,只有一个服务的localDataService状态更改了(少了001用户)。
其他实例的本地数据仍有001用户,下一次查询的结果会有两种,一种结果是返回用户没找到,另一个结果是返回001用户,这就跟我们预期的不一致,导致了业务错误。
No.3 可缓存
应用缓存是一种提高性能的手段。这里官方也没有特别说明,其实符合普遍对资源的缓存的理解。
针对某个接口,可以设置请求头Cache-Control, 可以是no-cache 或者max-age= 60 (某个数值)去告诉接口请求方可以缓存该接口数据多久。
举个例子,我们进入百度搜索RFC 7234(http协议关于缓存的标准)可以看到“消息头”里面关于缓存的设置,百度把一些不经常变化的资源标记了很大值,告诉火狐浏览器,下次不需要重新下载该资源。
刷新页面,我们可以拿上面缓存的url进行搜索,这里显示已经缓存了。
No.4 统一接口/标准化接口
官网提了,接口定义需要满足4个约束:资源标示、通过呈现方式来操作资源、资源自描述、以及使用超媒体作为应用状态。
举个例子,像设计 /api/user/<userId> 这个接口,如何符合第四原则满足统一标准化接口?
首先,/api/user 这个uri定位了用户资源相关的信息操作,然后可以用GET来查询用户信息,DELETE来删除用户信息。
再说资源自描述,我们查询 /api/user/1001, 获得一下响应消息:
{
"id":"1001",
"name":"levin",
"country": "China"
}
像这样的就是资源自描述,这个响应告诉了我们用户信息的属性,结构。我们查询其他人像小明,也是获取类似的反馈。
至于超媒体作为应用状态,这个不需要多说,简单理解为多种格式的响应消息作为状态即可。
这个设计原则,更多约束目标系统的REST API更加容易操作,可见可读性。
No.5 分层次系统风格
这里就是架构的分层,每层内的组件不允许看到与其不直接交互的层,也就是说,非直接交互的层对当前层内的组件不可访问。
比如上图,A <->B<->C,三层架构中,AB紧邻,BC紧邻,这样分层避免了AC直接交流,A层的变动不会对C层造成影响。
这样设计可以更好的让层与层之间互相解耦,让每一层专注做单层的功能(当然分层数过多也有坏处)。
No.6 按需编码(可选)
服务器可以提供一些代码或者脚本并在客户的运行环境中执行, 比如一些JS脚本供客户端下载调用。
像过去Java服务器端可以生成Applet脚本供客户端执行。
说到这里跟按需编码(CodeOnDemand), 好像表达的意思不太贴切,我们只需要明白这个设计原则是为了提高客户端的扩展性即可。
上面说那么多,其实RESTful API本质是通过走HTTP协议来呈现接口。
通过uri为系统交互的接口,比直接代码调用代码更加简化,而且脱离了语言约束。