Skip to content
快速预览

RESTfulAPI设计规范

✍️ w 🕒 2023-06-06 08:22:52(4 months ago) 🔗 F.开发中杂七杂八 restful api 设计规范

REST这个词,是Roy Thomas Fielding在他2000年的博士论文中提出的。他是HTTP协议(1.0版和1.1版)的主要设计者、Apache服务器软件的作者之一、Apache基金会的第一任主席

RESTful(Representational State Transfer)是一种软件架构风格,它以资源为中心,通过 HTTP(超文本传输协议)提供一组简单、统一、易于使用的接口,目的是为了解决 Web 应用中分布式系统的互操作性和可扩展性问题。

类似的风格还用

RESTful 是一种符合 REST(Representational State Transfer)设计原则的软件架构风格。除了 RESTful 之外,还有许多其他的软件架构风格和设计约束,如:

  1. SOAP(Simple Object Access Protocol):SOAP 是一种基于 XML 的 Web 服务消息传递协议,规定了客户端和服务器之间如何调用和传递数据。

  2. RPC(Remote Procedure Call):RPC 是一种远程过程调用技术,允许一个程序在本地调用另一台计算机上的子程序,就像调用本地子程序一样。RPC 可以基于不同的传输协议(例如,HTTP、TCP/IP)和序列化协议(例如,XML、JSON、binary)实现。

  3. GraphQL:GraphQL 是一种数据查询语言,由 Facebook 提出,旨在提供更灵活且高效的数据查询方式。不同于 RESTful 的资源导向,GraphQL 通过一个统一的查询入口获取所需的所有数据,客户端可以准确地请求所需的数据,避免了数据过度或不足的情况。

  4. OData(Open Data Protocol):OData 是一种基于REST架构风格的数据访问协议,定义了客户端和服务器进行数据通信的标准,主要用于构建丰富的数据API。与 RESTful 类似,OData 遵循无状态、可缓存等原则,同时提供了更多的查询和操作功能。

  5. Message Queue Architecture:这是一种采用消息队列的通信方式进行协调和通信的架构。一个组件产生消息并输送至消息队列,由另一个组件消费消息。这种方式有利于降低组件间的耦合性,提高扩展性,同时保证了消息的顺序以及可靠传输。

  6. Event-Driven Architecture:事件驱动架构是一种组件通过发送、接收和处理事件进行通信和协调的架构。组件在事件发生时被动地获取并处理事件,有利于提高系统的响应能力和扩展性。

以上各种架构风格各有优劣,根据不同的应用场景和需求进行选择。

介绍 Representational State Transfer

  • Representational:表示。RESTful 是一种用于 Web 服务的架构风格,可以通过 HTTP 协议来表示和传输 Web 资源。资源可以是任何东西,比如 HTML 页面,JSON、XML 格式的数据 也可以是一段文本、一张图片、一首歌曲、一种服务,总之就是一个具体的实在等。资源可以通过 URI 进行访问,而 HTTP 协议可以表示和传输这些资源

URI

http://www.example.com/index.html 是一个URI,其中http是协议,www.example.com是主机名,/index.html是路径。URI是Web中最重要的概念之一,它使得我们能够在互联网上定位和访问各种资源。

URI只代表资源的实体,不代表它的形式。严格地说,有些网址最后的".html"后缀名是不必要的,因为这个后缀名表示格式,属于"表现层"范畴,而URI应该只代表"资源"的位置。它的具体表现形式,应该在HTTP请求的头信息中用Accept和Content-Type字段指定,这两个字段才是对"表现层"的描述。

  • State Transfer:状态转移。RESTful 通过 HTTP 协议的各种方法(比如 GET、POST、DELETE 等)来实现资源状态的转移(转换),并以 HTTP 的方式传输这些转移后得到的资源信息。客户端和服务端之间通过 HTTP 协议交互,服务端不需要关心客户端的具体实现,只需要关注资源的状态,并提供 API 接口,让客户端通过 URI 进行访问。

Representational State Transfer(REST)是一种软件架构风格,它主要用于Web服务的设计和实现。REST основ于客户端-服务器架构,提倡可以缓存的无状态通信。在REST中,资源是通过URL(统一资源定位符)进行标识,可通过HTTP动词(如GET, POST, PUT, DELETE等)进行资源操作。因此他的特点:

  1. 资源导向:REST是基于资源的,将Web应用中的所有实体定位为资源,通过URL标识。

  2. 无状态:客户端与服务器之间的通信应该保持无状态,即每个请求都包含处理请求所需的所有信息。这使得响应可以方便地缓存,并提高了系统的可扩展性。同时,无状态意味着服务器无需维护客户端的上下文信息。

  3. 客户端-服务器架构:客户端负责用户界面和操作,服务器负责实现业务逻辑和数据管理。因此,客户端和服务器的职责清晰分离。

  4. 缓存:通过对响应结果进行缓存,可以降低服务器的负载,提高响应速度,以及减小延迟。

  5. 使用标准HTTP方法:REST架构使用标准HTTP方法进行资源操作,如GET、POST、PUT、DELETE等。这些方法能够清晰地描述对资源的操作行为。

  6. 易于扩展和整合:REST易于扩展的架构使其能够方便地整合其他软件模块。

  7. 高可用性和可靠性:由于REST的无状态设计、缓存机制和易于扩展性,它可以提供更高的可用性和可靠性。

规则规范

  1. 使用HTTP协议:RESTful API应该基于HTTP协议,因为它本身是对Web的资源操作,而HTTP是Web领域的通行协议,但是安全性与权限控制:API应该考虑访问安全性,建议使用HTTPS协议。同时,通过OAuth、API密钥等方式进行访问授权和权限控制

  2. URL定位资源:API的URL应该代表一个具体的资源,尽量使用名词(而非动词)来表示资源,例如:/users而不是/getUsers。此外,建议使用复数形式来表示资源集合,将资源名与数据库的表名或模型匹配。这样设计能确保资源在 API 和数据库中的一致性,并使得在 API 层处理底层数据更为直接和容易,API中的名词也应该使用复数。假设有一个用户表(User),URL 资源可能是这样设计的:

GET /users            // 获取所有用户
GET /users/:id        // 获取指定 ID 的用户
POST /users           // 创建新用户
PUT /users/:id        // 更新指定 ID 的用户
DELETE /users/:id     // 删除指定 ID 的用户
  1. 使用HTTP谓词表示操作:API应该使用标准的HTTP谓词(GET、POST、PUT、DELETE等)来表示对资源的操作,而不是通过URL中的动词实现。常用的HTTP动词有下面五个(括号里是对应的SQL命令)。
    • GET(读取):从服务器取出资源(一项或多项)。
    • POST(创建):在服务器新建一个资源。
    • PUT(完整更新):在服务器更新资源(客户端提供改变后的完整资源)。
    • PATCH(部分更新):在服务器更新资源(客户端提供改变的属性)。
    • DELETE(删除):从服务器删除资源。
PUTPATCH 都是用于更新资源的 HTTP 方法,但它们之间存在一些关键区别:

1. PUT 方法

  • PUT 方法是幂等的,这意味着无论调用多少次,结果总是相同。这为客户端提供了更可靠的更新方式,尤其是在网络不稳定的环境中。
  • PUT 方法要求客户端提供整个资源的全部属性进行更新。换句话说,客户端需要发送资源的所有字段,无论它们是否发生改变。服务器端接收到请求后,将使用一份新的完整数据来替换旧数据。

2. PATCH 方法

  • PATCH 方法是非幂等的,每次调用的结果都可能不同。这使得客户端需要更小心地处理请求错误和重试操作。
  • PATCH 方法允许客户端部分更新资源。客户端只需发送需要修改的字段,而不是整个资源。服务器端接收到请求后,仅更新请求中指定的字段。

举例说明:假设有一个用户资源,由 idnameemail 三个字段组成。如果我们要更新用户的 email 字段:

使用 PUT 方法更新

http
PUT /users/1
{
   "name": "John Doe",
   "email": "john.doe@example.com"
}

注意,尽管我们只更新了 email 字段,但要发送整个资源。

使用 PATCH 方法更新

http
PATCH /users/1
{
   "email": "john.doe@example.com"
}

只需发送要更新的字段。

总结:PUTPATCH 的主要区别在于它们更新资源的方式。使用 PUT 时,需要提供完整的资源数据,而使用 PATCH 时,只需提供需要更新的字段。另外,PUT 是幂等的,而 PATCH 是非幂等的。在实际应用中,选择使用哪种方法取决于具体需求和场景。

其他不常用的HTTP动词

  • HEAD:获取资源的元数据。
  • OPTIONS:获取信息,关于资源的哪些属性是客户端可以改变的。
  1. 资源层次结构:如果资源之间存在层次关系,可以在URL中通过 '/' 表示。例如,获取一个用户的订单列表可以使用:
例子
  • 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:删除某个指定动物园的指定动物
  1. 使用查询参数进行筛选和分页:当需要对资源进行筛选或分页时,可以使用URL的查询参数实现。例如:/users?gender=male&page=2表示筛选出男性用户且获取第二页的列表。
例子
  • ?limit=10:指定返回记录的数量
  • ?offset=10:指定返回记录的开始位置。
  • ?page=2&per_page=100:指定第几页,以及每页的记录数。
  • ?sortby=name&order=asc:指定返回结果按照哪个属性排序,以及排序顺序。
  • ?animal_type_id=1:指定筛选条件

参数的设计允许存在冗余,即允许API路径和URL参数偶尔有重复。比如,GET /zoo/ID/animals 与 GET /animals?zoo_id=ID 的含义是相同的。

  1. 状态码表示返回结果:合理使用HTTP状态码来表示操作的结果。HTTP 状态码就是一个三位数,分成五个类别:
    • 1xx:相关信息
    • 2xx:操作成功
    • 3xx:重定向
    • 4xx:客户端错误
    • 5xx:服务器错误 这五大类总共包含100多种状态码,覆盖了绝大部分可能遇到的情况。每一种状态码都有标准的(或者约定的)解释,客户端只需查看状态码,就可以判断出发生了什么情况,所以服务器应该返回尽可能精确的状态码。

常见的有以下一些(方括号中是该状态码对应的 HTTP 动词)状态码的完全列表参见这里:

  • 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 - [*]:服务器发生错误,用户将无法判断发出的请求是否成
  1. 返回结构统一与描述清晰:返回结果应该具有清晰的结构,且字段描述清晰。建议使用JSON作为返回数据格式,方便跨平台通信。针对不同操作,服务器向用户返回的结果应该符合以下规范,前面都是讲的请求规范,请求之后的数据规范对应不同的操作应该有相应的返回:

    • GET /collection:请求获取一个资源对象列表,响应返回一个包含多个资源对象的数组。例如,GET /users 可以返回所有用户的列表。
    • GET /collection/resource:请求获取一个单独的资源对象,响应返回一个单独的资源对象。例如,GET /users/1 可以返回ID为1的用户对象。
    • POST /collection:请求创建一个新的资源对象,响应返回新创建的资源对象。例如,POST /users 可以创建一个新的用户对象,并返回该新用户对象。
    • PUT /collection/resource:请求更新一个完整的资源对象,响应返回更新后的完整资源对象。例如,PUT /users/1 可以更新ID为1的用户对象,并返回更新后的完整用户对象。
    • PATCH /collection/resource:请求更新一个部分的资源对象,响应返回更新后的完整资源对象。例如,PATCH /users/1 可以更新ID为1的用户对象的某些属性,并返回更新后的完整用户对象。
    • DELETE /collection/resource:请求删除一个资源对象,响应返回一个空文档。例如,DELETE /users/1 可以删除ID为1的用户对象,并返回一个空文档。
  2. 版本控制:为了兼容旧版本应用和用户,可以通过URL的前缀或者请求头中的Accept参数来控制API的版本。例如:

Details

/v1/users

Accept: application/vnd.company.myapp-v3+json。

Accept: vnd.example-com.foo+json; version=1.0

Accept: vnd.example-com.foo+json; version=1.1

Accept: vnd.example-com.foo+json; version=2.0

两者的区别在一个是在 URL 中声明版本,一种是在请求头中声明版本

注意点

有一种不恰当的做法是,即使发生错误,也返回200状态码,把错误信息放在数据体里面,就像下面这样。代码中,解析数据体以后,才能得知操作失败。

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

{
  "status": "failure",
  "data": {
    "error": "Expected at least two items in list."
  }
}

这种做法实际上取消了状态码,这是完全不可取的。正确的做法是,状态码反映发生的错误,具体的错误信息放在数据体里面返回。下面是一个例子

HTTP/1.1 400 Bad Request
Content-Type: application/json

{
  "error": "Invalid payoad.",
  "detail": {
     "surname": "This field is required."
  }
}

URI包含动词。因为"资源"表示一种实体,所以应该是名词,URI不应该有动词,动词应该放在HTTP协议中,某个URI是/posts/show/1,其中show是动词,这个URI就设计错了,正确的写法应该是/posts/1,然后用GET方法表示show。

但如果某些动作是HTTP动词表示不了的应该把动作做成一种资源

POST /accounts/1/transfer/500/to/2

正确的写法是把动词transfer改成名词transaction,资源不能是动词,但是可以是一种服务:

POST /transaction HTTP/1.1
Host: 127.0.0.1
  
from=1&to=2&amount=500.00

同理筛选(Filtering)、排序(Sorting)、分页(Pagination)、字段选择接口URL尽量保持简短,对于集合资源不同纬度的筛选,可通过组合不同的Query Param来实现。

GET /v1/externalEmployees           [Bad]
GET /v1/internalEmployees           [Bad]
GET /v1/internalAndSeniorEmployees  [Bad]

GET /v1/employee?state=internal&title=senior  [OK]

使用小写字母,多个单词用"-"分隔,提高URL的可读性,RFC3986 定义了URI是大小写敏感的(除了scheme和host部分),为了避免歧义,接口路径应尽量使用小写字母。

对于由多个单词构成的路径,推荐使用'-'(hyphen)分隔,避免使用驼峰命名(camelCase)、下划线命名(snake_case)、首字母大写的驼峰命名(PascalCase),以提高可读性,

camelCase  :  /v1/customer/1000/shippingAddress  [Bad]
snake_case :  /v1/customer/1000/shipping_address [Bad]
PascalCase :  /v1/customer/1000/ShippingAddress  [Bad]

hyphen     :  /v1/customer/1000/shipping-address [OK]

资源嵌套层次避免过深,尽量不超过2层

GET  /v1/user/1000/company    查询ID等于1000的用户的公司信息
GET  /v1/user/1000/company/10 查询ID等于1000的用户所属公司中ID=10的公司信息,嵌套两层

参考

https://www.yuque.com/lipengzhou/dev/poh2gw

理解RESTful架构

RESTful API 设计指南

一文搞定 RESTful 规范

RESTful API 实践

Released under the MIT License.