REST API 设计规范

在项目中往往会需要确定一个好的API风格,到底有哪些风格可以参考,API Style 的细节要点有哪些呢?

Http API Style 有哪些?

  • SOAP:tend to be centered around operations that are usually use-case specific and specialized.
  • REST:centered around business (data) entities exposed as resources that are identified via URIs and can be manipulated via standardized CRUD-like methods using different representations, and hypermedia
  • GraphQL:a query language for APIs and a runtime for fulfilling those queries with your existing data

SOAP 风格(严格来说,算不上风格)最早于1998年由微软提出;REST 风格于2000年 由 Roy Thomas Fielding 论文中提出;GraphQL 于2015年由 Facebook 提出;

  • SOAP vs REST

如果要轻松、快速地完成API设计,SOAP 风格的API就足够了。毕竟REST有时很难做到,尤其是在一开始。但随着时间的推移,使用RESET 风格的API,服务器端的演进变得更容易,客户机对变化的适应能力也更强。

  • REST vs GraphQL

    REST 限于其历史背景,对于 查询 操作一些细节并没有太多描述,随着互联网的发展,查询的复杂度越来越高,而 GraphQL 是一个很好的补充。

在业界有将近70%的API是REST-like的风格,其中当然就包括谷歌、微软等行业巨头,REST 差不多已经成为了事实上的标准,了解、用好 REST 十分必要。

REST 是什么?

REST,全称是 Resource Representational State Transfer:通俗来讲就是:资源在网络中以某种表现形式进行状态转移。分解开来:

  • Resource:资源,即数据(前面说过网络的核心)。比如 newsfeed,friends等;
  • Representational:某种表现形式,比如用JSON,XML,JPEG等;
  • State Transfer:状态变化。通过HTTP动词实现。

资源模型

资源是具有类型、数据、与其他资源关系、以及一组对其进行操作的方法的对象。

Richardson成熟度模型

Level 0

不使用任何URI,HTTP方法和HATEOAS功能。

该模型的出发点是使用HTTP作为远程交互的传输系统,但不使用Web的任何机制。基本上就是使用HTTP作为你远程交互机中的隧道机制,通常基于“远程过程调用“(RPC, Remote Procedure Invocation )。

Level 1 – Resources

使用URI、HTTP方法、HATEOAS中的 URI

迈向REST的第一步就是引入资源的概念。接下来,我们所要讨论的是各个资源,而不是将所有请求发送到单一的服务端点。每个资源都由唯一的URI单独标识

Level 2 – HTTP Verbs

使用URI、HTTP方法、HATEOAS中 的URI和HTTP

支持每个公开资源上的几个HTTP谓词 – 创建,读取,更新和删除(CRUD)服务。通常代表业务实体的资源状态可以通过网络进行操作。

Level 3 – Hypermedia Controls

使用所有三个,即URI,HTTP和HATEOAS。

超媒体控制的关键在于它告诉我们下一步我们可以做什么,以及操作所需资源的URI。与我们必须提前知道在哪里创建预约请求不同(Level2中),在响应中的 HATEOAS 告诉了我们下一步该如何做,以完成应用程序状态转换。

关键问题

REST 本身不是标准,只是一种风格。因此只要遵从该风格,都是OK的。然而,除此之外我们逃不开使用中遇到的很多问题,最典型的问题,如下:

  • Error Handling
  • Sorting
  • Pagination
  • versioning
  • filtering
  • Long running
  • Sub-collection
  • Action(i.e. Batch Operation)

Error handling

详见:跨服务错误处理

Sorting

如果 API 方法允许客户端指定列表结果的排序顺序,则请求消息 应该 包含一个字段:

string order_by = ...;

说明:语法中的冗余空格字符是无关紧要的。 "foo,bar desc"" foo , bar desc " 是等效的。

字符串值 应该 遵循 SQL 语法:逗号分隔的字段列表。例如: "foo,bar" 。默认排序顺序为升序。要将字段指定为降序, 应该 将后缀 " desc" 附加到字段名称中。例如: "foo desc,bar"

Pagination

  • 可列表集合 应该 支持分页,即使结果通常很小。

说明:如果某个 API 从一开始就不支持分页,稍后再支持它就比较麻烦,因为添加分页会破坏 API 的行为。 不知道 API 正在使用分页的客户端可能会错误地认为他们收到了完整的结果,而实际上只收到了第一页。

  • 翻页方式

    后台存储 Request Response
    搜索引擎 {
    “page_num”: 1, // 页码从 1 开始
    “page_size”: 10
    }
    {
    “code”: 0,
    “msg”: “”,
    “data”: {
    “total_cnt”: 100,
    “items”: []
    }
    }
    数据库 {
    “last_id”: 1, // 第一页,默认传0
    “page_size”: 10
    }
    {
    “code”: 0,
    “msg”: “”,
    “data”: {
    “items”: [],
    “next_id”: 10 // 下一页放到last_id的值
    }
    }

Versioning

业界方案

  • 无版本控制

    这是最简单的方法,它对于一些内部 API 来说可能是可以接受的。 重大变化可以表示为新资源或新链接。 向现有资源添加内容可能不会带来重大更改,因为不希望看到此内容的客户端应用程序将忽略它。

    https://adventure-works.com/customers/3
  • URI 版本控制

    每次修改 Web API 或更改资源的架构时,向每个资源的 URI 添加版本号。 以前存在的 URI 应像以前一样继续运行,并返回符合原始架构的资源。

    https://adventure-works.com/v2/customers/3
  • 查询字符串版本控制

    不是提供多个 URI,而是可以通过在追加到 HTTP 请求后面的查询字符串中使用参数来指定资源的版本,例如

    https://adventure-works.com/customers/3?version=2

    注意:某些较旧的 Web 浏览器和 Web 代理不会缓存在 URI 中包含查询字符串的请求的响应。 这可能会降低使用 Web API 并在此类 Web 浏览器中运行的 Web 应用程序的性能。

  • 标头版本控制

    不是追加版本号作为查询字符串参数,而是可以实现指示资源的版本的自定义标头。 此方法需要客户端应用程序将相应标头添加到所有请求,虽然如果省略了版本标头,处理客户端请求的代码可以使用默认值(版本 1)。 下面的示例使用了名为 Custom-Header 的自定义标头**。 此标头的值指示 Web API 的版本。

    GET https://adventure-works.com/customers/3 HTTP/1.1
    Custom-Header: api-version=1
  • 无版本控制:在更改RESTful API时,请以兼容的方式进行更改,并避免生成其他API版本。

    说明:多个版本会使理解、测试、维护、发展、操作和发布我们的系统变得非常复杂。

在更改RESTful api时,请以兼容的方式进行更改,并避免生成其他API版本。多个版本可能会显著地复杂化查看、测试、维护、发展、运营和发布系统( 补充阅读 )。如果无法以兼容的方式更改API,请使用这三种方式:

  • 在旧资源变体的基础上创建新资源(变量)
  • 创建一个新的服务端点-即一个具有新API的新应用程序(使用新域名)
  • 在微服务中创建一个新的API版本,该版本支持与旧API同时支持

Filtering

在参数过滤时通常会为参数值定义数据格式。为了在所有 API 中提供一致的开发者体验并减少学习曲线,API 设计人员 必须 使用以下 扩展巴科斯范式 (Extended Backus-Naur Form,简写为“EBNF”)语法的变体来定义这样的语法:

Production  = name "=" [ Expression ] ";" ;
Expression  = Alternative { "|" Alternative } ;
Alternative = Term { Term } ;
Term        = name | TOKEN | Group | Option | Repetition ;
Group       = "(" Expression ")" ;
Option      = "[" Expression "]" ;
Repetition  = "{" Expression "}" ;

注意: TOKEN 表示在语法之外定义的终端符号。

Example

GET /zoos?id=1001,1002,1003

Long running

有时,POST、PUT、PATCH 或 DELETE 操作可能需要一段时间才能完成。如果需要等待该操作完成后才能向客户端发送响应,可能会造成不可接受的延迟。在这种情况下,请考虑将该操作设置为异步操作。返回 HTTP 状态代码 202(已接受),指示该请求已接受进行处理,但尚未完成。

应公开一个可返回异步请求状态的终结点,使客户端能够通过轮询状态终结点来监视状态。在 202 响应的 Location 标头中包含状态终结点的 URI。例如:

HTTP/1.1 202 Accepted
Location: /api/status/12345

如果客户端向此终结点发送 GET 请求,响应中应包含该请求的当前状态。(可选)响应中还可以包含预计完成时间,或者用于取消操作的链接

HTTP/1.1 200 OK
Content-Type: application/json

{
    "status":"In progress",
    "link": { "rel":"cancel", "method":"delete", "href":"/api/status/12345" }
}

如果异步操作创建了新资源,则该操作完成后,状态终结点应返回状态代码 303(查看其他)。在 303 响应中,包含一个 Location 标头用于提供新资源的 URI:

HTTP/1.1 303 See Other
Location: /api/orders/12345

有关详细信息,请参阅 异步请求-回复模式

Sub-collection

有时,API 需要让客户跨子集执行 List/Search 操作。例如,“API 图书馆”有一组书架,每个书架都有一系列书籍,而客户希望在所有书架上搜索某一本书。在这种情况下,建议在子集合上使用标准 List ,并为父集合指定通配符集合 ID "-" 。对于“API 图书馆”示例,我们可以使用以下 REST API 请求:

GET https://library.googleapis.com/v1/shelves/-/books/{id}

注意:选择 "-" 而不是 "*" 的原因是为了避免需要进行 URL 转义。

Action

常用的HTTP动词有下面五个(括号里是对应的SQL命令)。

  • GET(SELECT):从服务器取出资源(一项或多项)。
  • POST(CREATE):在服务器新建一个资源。
  • PUT(UPDATE):在服务器更新资源(客户端提供改变的属性)
  • DELETE(DELETE):从服务器删除资源。

对于非标准的操作,以上动词无法无法满足需求,可以在资源上使用“操作”子集合。 动作基本上类似于RPC的消息,用于对资源执行特定操作。 “动作”子集合可以看作是一个命令队列,可以将新的动作发布到该命令队列中,然后由API执行。定义标准动词如下:

  1. batch:批量操作
  2. search:搜索操作
GET /zoos:列出所有动物园
POST /zoos:新建一个动物园
GET /zoos/ID:获取某个指定动物园的信息
PUT /zoos/ID:更新某个指定动物园的信息(提供该动物园的全部信息)
PATCH /zoos/ID:更新某个指定动物园的信息(提供该动物园的部分信息)
DELETE /zoos/ID:删除某个动物园
GET /zoos/ID/animals:列出某个指定动物园的所有动物
DELETE /zoos/ID/animals/ID:删除某个指定动物园的指定动物
GET /zoos/-/action/batch  批量查询
POST /zoos/-/action/batch 批量更新
POST /zoos/-/action/search 搜索

其他

  • null 和不存在的属性使用相同的语义

    required nullable {} {“example”:null}
    true true ✗ No ✔ Yes
    false true ✔ Yes ✔ Yes
    true false ✗ No ✗ No
    false false ✔ Yes ✗ No
  • 路径使用 中划线 - 代替 下划线 _

    在搜索引擎中,把中划线当做空格处理,而下划线是被忽略的。使用中划线是对搜索引擎友好的写法

    Example:

    /shipment-orders/{shipment-order-id}
  • 范围

    表示范围的字段 应该 使用半开区间和命名惯例 [start_xxx, end_xxx) ,例如 [start_key, end_key)[start_time, end_time) 。通常 C ++ STL 库和 Java 标准库会使用半开区间语义。API 应该 避免使用其他表示范围的方式,例如 (index, count)[first, last]

总结

完成以上这些,也仅仅是达到REST Level 2,由于Level 3 对于API风格影响不大,暂不涉及。对 HATEOAS 感兴趣,可以参考 Github v3 版本的API。

参考链接

本文作者:cyningsun

本文地址: https://www.cyningsun.com/06-29-2020/how-to-write-restful-api.html

版权声明:本博客所有文章除特别声明外,均采用 CC BY-NC-ND 3.0 CN 许可协议。转载请注明出处!