Restful API 架构与设计参考原则

2023-05-19,,

1. 什么是REST
REST全称是Representational State Transfer,中文意思是表述(编者注:通常译为表征)性状态转移。 它首次出现在2000年Roy Fielding的博士论文中,Roy Fielding是HTTP规范的主要编写者之一。 他在论文中提到:"我这篇文章的写作目的,就是想在符合架构原理的前提下,理解和评估以网络为基础的应用软件的架构设计,得到一个功能强、性能好、适宜通信的架构。REST指的是一组架构约束条件和原则。" 如果一个架构符合REST的约束条件和原则,我们就称它为RESTful架构。

REST本身并没有创造新的技术、组件或服务,而隐藏在RESTful背后的理念就是使用Web的现有特征和能力, 更好地使用现有Web标准中的一些准则和约束。虽然REST本身受Web技术的影响很深, 但是理论上REST架构风格并不是绑定在HTTP上,只不过目前HTTP是唯一与REST相关的实例。 所以我们这里描述的REST也是通过HTTP实现的REST。

什么是RESTful架构:

每一个URI代表一种资源;
客户端和服务器之间,传递这种资源的某种表现层;
客户端通过四个HTTP动词(GET/POST/PUT/DELETE),对服务器端资源进行操作,实现”表现层状态转化”。

URI
要让一个资源可以被识别,需要有个唯一标识,在Web中这个唯一标识就是URI(Uniform Resource Identifier)。 URI既可以看成是资源的地址,也可以看成是资源的名称。如果某些信息没有使用URI来表示,那它就不能算是一个资源, 只能算是资源的一些信息而已。URI的设计应该遵循可寻址性原则,具有自描述性,需要在形式上给人以直觉上的关联。

HTTP动词

1. 使用GET、POST、PUT、DELETE这几种请求模式
请求模式也可以说是动作、数据传输方式,通常我们在web中的form有GET、POST两种,而在HTTP中,存在下发这几种。

GET (选择):从服务器上获取一个具体的资源或者一个资源列表。 
POST (创建): 在服务器上创建一个新的资源。
PUT(更新):以整体的方式更新服务器上的一个资源。 
PATCH (更新):只更新服务器上一个资源的一个属性。
DELETE(删除):删除服务器上的一个资源。

1.1 使用名词而不是动词
Resource
资源

GET
读 POST
创建 PUT
修改 DELETE
/cars 返回 cars集合 创建新的资源 批量更新cars 删除所有cars
/cars/711 返回特定的car 该方法不允许(405) 更新一个指定的资源 擅长指定资源
不要使用:

/getAllCars
/createNewCar
/deleteAllRedCars

1.2 Get方法和查询参数不应该涉及状态改变
使用PUT, POST 和DELETE 方法 而不是 GET 方法来改变状态,不要使用GET 进行状态改变:

GET /users/711?activate 
GET /users/711/activate

1.3 使用复数名词
不要混淆名词单数和复数,为了保持简单,只对所有资源使用复数。

/cars 而不是 /car
/users 而不是 /user
/products 而不是 /product
/settings 而部署 /setting

1.4 使用子资源表达关系
如果一个资源与另外一个资源有关系,使用子资源:

GET /cars/711/drivers/ 返回 car 711的所有司机
GET /cars/711/drivers/4 返回 car 711的4号司机

2. 为集合提供过滤 排序 选择和分页等功能
比如在数据过多, 需要对数据进行分页请求的时候, 我们应该统一 API 请求参数. 常见的有这些.

limit=10 指定返回记录的数量
offset=10 指定返回记录的开始位置。
page=2&per_page=100 指定第几页,以及每页的记录数。
sortby=name&order=asc 指定返回结果按照哪个属性排序,以及排序顺序。
animal_type_id=1 指定筛选条件
Filtering过滤:

使用唯一的查询参数进行过滤:

GET /cars?color=red 返回红色的cars
GET /cars?seats<=2 返回小于两座位的cars集合

Sorting排序:

允许针对多个字段排序

GET /cars?sort=-manufactorer,+model

这是返回根据生产者降序和模型升序排列的car集合

Field selection

移动端能够显示其中一些字段,它们其实不需要一个资源的所有字段,给API消费者一个选择字段的能力,这会降低网络流量,提高API可用性。

GET /cars?fields=manufacturer,model,id,color

Paging分页

使用 limit 和offset.实现分页,缺省limit=20 和offset=0;

GET /cars?offset=10&limit=5

为了将总数发给客户端,使用订制的HTTP头: X-Total-Count.

链接到下一页或上一页可以在HTTP头的link规定,遵循Link规定:

Link: <https://blog.mwaysolutions.com/sample/api/v1/cars?offset=15&limit=5>; rel="next",
<https://blog.mwaysolutions.com/sample/api/v1/cars?offset=50&limit=3>; rel="last",
<https://blog.mwaysolutions.com/sample/api/v1/cars?offset=0&limit=5>; rel="first",
<https://blog.mwaysolutions.com/sample/api/v1/cars?offset=5&limit=5>; rel="prev",

3. 版本化你的API
API的开发直接关系了APP是否可以正常使用,如果原本运行正常的API,突然改动,那么之前使用这个API的APP可能无法正常运行。APP是不可能强迫用户主动升级的,因此,通过API版本来解决这个问题。也就是说,API的多个版本是同时运行的,而且都要保证可以正常使用。

按照RESTful的规范,不同的版本也应该用相同的API URL,通过header信息来判断版本,再调用不同版本的程序进行处理。但是这明显会给开发带来巨大的成本。使得API版本变得强制性,不要发布无版本的API,使用简单数字,避免小数点如2.5.

域名

应该尽量将API部署在专用域名之下,如:https://api.example.com

也可以放在主域名下:https://example.org/api/

版本

不同的版本,用不同的URL来提供服务,在URL中通过v1、v2来区分版本号,比如v2.api.xxx.com/user的方式,或者http://api.domain.com/v2  或者http://www.domain.com/api/v2 。

放入到头信息的Accept中
制定版本并在版本之间平缓过渡对于设计和维护一套API是个巨大的挑战。所以,最好在设计之初就使用一些方法来预防可能会遇到的问题。
为了避免API的变动导致用户使用中产生意外结果或调用失败,最好强制要求所有访问都需要指定版本号。请避免提供默认版本号,一旦提供,日后想要修改它会相当困难。
最适合放置版本号的位置URL中,或者是头信息(HTTP Headers)中在 Accept 段中使用自定义类型(content type)与其他元数据(metadata)一起提交。

https://api.example.com/v1/

Accept: application/vnd.heroku+json; version=3
提供 Request-Id

为每一个请求响应包含一个Request-Id字段,并使用UUID作为该值。通过在客户端、服务器或任何支持服务上记录该值,它能主我们提供一种机制来跟踪、诊断和调试请求。

4 .json数据类型
Number:整数或浮点数
String:字符串
Boolean:true 或 false
Array:数组包含在方括号[]中
Object:对象包含在大括号{}中
Null:空类型
所以,传输的数据类型不能超过这六种数据类型。以前,我们曾经试过传输Date类型,它会转为类似于"2016年1月7日 09时17分42秒 GMT+08:00"这样的字符串,这在转换时会产生问题,不同的解析库解析方式可能不同,有的可能会转乱,有的可能直接异常了。要避免出错,必须做特殊处理,自己手动去做解析。为了根除这种问题,最好的解决方案是用毫秒数或者字符串表示日期。

使用详细的错误包装错误:

{

"errors": [

{

"userMessage": "Sorry, the requested resource does not exist",

"internalMessage": "No car found in the database",

"code": 34,

"more info": "http://dev.mwaysolutions.com/blog/api/v1/errors/12345"

}

]

}

返回的数据结构

{
code:0,
message: "success",
data: { key1: value1, key2: value2, ... }
}

code: 返回码,0表示成功,非0表示各种不同的错误
message: 描述信息,成功时为"success",错误时则是错误信息
data: 成功时返回的数据,类型为对象或数组

data字段只在请求成功时才会有数据返回的。数据类型限定为对象或数组,当请求需要的数据为单个对象时则传回对象,当请求需要的数据是列表时,则为某个对象的数组。这里需要注意的就是,不要将data传入字符串或数字,即使请求需要的数据只有一个,比如token,那返回的data应该为:

// 正确 data: { token: abcdedf }

9. 使用Http状态码处理错误
不同错误需要定义不同的返回码,属于客户端的错误和服务端的错误也要区分,比如1XX表示客户端的错误,2XX表示服务端的错误

如果你的API没有错误处理是很难的,只是返回500和出错堆栈不一定有用

Http状态码提供70个出错,我们只要使用10个左右:

200 OK - [GET]:服务器成功返回用户请求的数据,该操作是幂等的(Idempotent)。
201 CREATED - [POST/PUT/PATCH]:用户新建或修改数据成功。
202 Accepted - [*]:表示一个请求已经进入后台排队(异步任务)
204 NO CONTENT - [DELETE]:用户删除数据成功。
400 INVALID REQUEST - [POST/PUT/PATCH]:用户发出的请求有错误,服务器没有进行新建或修改数据的操作,该操作是幂等的。
401 Unauthorized - [*]:表示用户没有权限(令牌、用户名、密码错误)。
403 Forbidden - [*] 表示用户得到授权(与401错误相对),但是访问是被禁止的。
404 NOT FOUND - [*]:用户发出的请求针对的是不存在的记录,服务器没有进行操作,该操作是幂等的。
406 Not Acceptable - [GET]:用户请求的格式不可得(比如用户请求JSON格式,但是只有XML格式)。
410 Gone -[GET]:用户请求的资源被永久删除,且不会再得到的。
422 Unprocesable entity - [POST/PUT/PATCH] 当创建一个对象时,发生一个验证错误。
500 INTERNAL SERVER ERROR - [*]:服务器发生错误,用户将无法判断发出的请求是否成功。

10.允许覆盖http方法
一些代理只支持POST 和 GET方法, 为了使用这些有限方法支持RESTful API,需要一种办法覆盖http原来的方法。

使用订制的HTTP头 X-HTTP-Method-Override 来覆盖POST 方法.

参考

https://segmentfault.com/a/1190000008697972#articleHeader3
http://blog.csdn.net/jasonhui512/article/details/53141390
http://www.cnblogs.com/kuyuecs/p/5949075.html
http://mclspace.com/2015/11/03/restful-note/
---------------------
作者:曾先森向前进
来源:CSDN
原文:https://blog.csdn.net/zwqjoy/article/details/78788915
版权声明:本文为博主原创文章,转载请附上博文链接!

Restful API 架构与设计参考原则的相关教程结束。

《Restful API 架构与设计参考原则.doc》

下载本文的Word格式文档,以方便收藏与打印。