RESTful API 设计指南与最佳实践 | RESTful API Design Guide and Best Practices

面向开发者的REST架构实践指南,涵盖HTTP方法、资源定义、幂等性原则与状态管理

什么是RESTful API?

REST (Representational State Transfer) 是一种软件架构风格,用于设计网络应用程序接口。REST本身并没有创造新的技术、组件或服务,而是利用Web的现有特征和能力,更好地使用现有Web标准中的准则和约束。虽然REST受Web技术影响深远,但理论上REST架构风格并不局限于HTTP协议,只是目前HTTP是实现REST的主要实例。本文主要描述基于HTTP实现的RESTful API设计原则。

REST架构的核心概念

REST架构基于以下几个核心概念构建:

  • 资源与URI

  • 统一资源接口

  • 资源的表述

  • 资源的链接

  • 状态的转移

1. 资源与URI (Uniform Resource Identifier)

资源定义

  • 任何事物,只要有被引用的必要,它就是一个资源。

  • 每个资源需要有唯一标识符,在Web中这个唯一标识就是URI。

  • URI既可视为资源的地址,也可视为资源的名称。

URI设计原则

URI的设计应遵循以下原则:

  • 可寻址性:每个资源都应有明确的地址

  • 自描述性:URI本身应提供资源信息

  • 直觉关联性:形式上应给人直观的关联

URI设计最佳实践

  • 使用下划线(_)或连字符(-)提高URI可读性

    /api/user-profiles  // 比 /api/userprofiles 更易读
  • 使用斜杠(/)表示资源的层级关系

    /api/repositories/owner/repo/issues
  • 使用问号(?)过滤资源

    /api/pulls?state=closed  // 查询已关闭的Pull Requests
  • 使用逗号(,)或分号(;)表示同级资源关系

    /api/users/tom,jerry  // 同时请求多个用户信息

2. 统一资源接口 (Uniform Interface)

无论资源类型如何,都应通过统一接口进行访问。RESTful API应使用标准HTTP方法,并遵循这些方法的语义。

幂等性原则

在编程中,幂等操作指任意多次执行所产生的影响与一次执行的影响相同。在REST API中:

  • 幂等方法:GET、HEAD、PUT、DELETE

  • 非幂等方法:POST

HTTP方法语义

GET

  • 特性:安全且幂等

  • 用途:获取资源表示

  • 缓存:变更时获取表示(支持缓存)

  • 状态码

    • 200 (OK) - 成功返回资源

    • 204 (No Content) - 资源有空表示

    • 301 (Moved Permanently) - 资源URI已更新

    • 303 (See Other) - 其他情况(如负载均衡)

    • 304 (Not Modified) - 资源未更改(缓存)

    • 400 (Bad Request) - 指代坏请求(如,参数错误)

    • 404 (Not Found) - 资源不存在

    • 406 (Not Acceptable) - 服务端不支持所需表示

    • 500 (Internal Server Error) - 通用错误响应

    • 503 (Service Unavailable) - 服务端当前无法处理请求

POST

  • 特性:不安全且不幂等

  • 用途

    • 使用服务端管理的(自动产生)的实例号创建资源

    • 创建子资源

    • 部分更新资源

    • 如果没有被修改,则不过更新资源(乐观锁)

  • 状态码

    • 200 (OK) - 如果现有资源已被更改

    • 201 (Created) - 如果新资源被创建

    • 202 (Accepted) - 已接受处理请求但尚未完成(异步处理)

    • 301 (Moved Permanently) - 资源的URI被更新

    • 303 (See Other) - 其他(如,负载均衡)

    • 400 (Bad Request) - 指代坏请求

    • 404 (Not Found) - 资源不存在

    • 406 (Not Acceptable) - 服务端不支持所需表示

    • 409 (Conflict) - 通用冲突

    • 412 (Precondition Failed) - 前置条件失败(如执行条件更新时的冲突)

    • 415 (Unsupported Media Type) - 接受到的表示不受支持

    • 500 (Internal Server Error) - 通用错误响应

    • 503 (Service Unavailable) - 服务当前无法处理请求

PUT

  • 特性:不安全但幂等

  • 用途

    • 用客户端管理的实例号创建一个资源

    • 通过替换的方式更新资源

    • 如果未被修改,则更新资源(乐观锁)

  • 状态码

    • 200 (OK) - 如果已存在资源被更改

    • 201 (Created) - 如果新资源被创建

    • 301 (Moved Permanently) - 资源的URI已更改

    • 303 (See Other) - 其他(如,负载均衡)

    • 400 (Bad Request) - 指代坏请求

    • 404 (Not Found) - 资源不存在

    • 406 (Not Acceptable) - 服务端不支持所需表示

    • 409 (Conflict) - 通用冲突

    • 412 (Precondition Failed) - 前置条件失败(如执行条件更新时的冲突)

    • 415 (Unsupported Media Type) - 接受到的表示不受支持

    • 500 (Internal Server Error) - 通用错误响应

    • 503 (Service Unavailable) - 服务当前无法处理请求

DELETE

  • 特性:不安全但幂等

  • 用途:删除资源

  • 状态码

    • 200 (OK) - 资源已被删除

    • 301 (Moved Permanently) - 资源的URI已更改

    • 303 (See Other) - 其他,如负载均衡

    • 400 (Bad Request) - 指代坏请求

    • 404 (Not Found) - 资源不存在

    • 409 (Conflict) - 通用冲突

    • 500 (Internal Server Error) - 通用错误响应

    • 503 (Service Unavailable) - 服务端当前无法处理请求

HTTP方法扩展

  • 统一接口并不阻止你扩展方法,只要方法对资源的操作有着具体的、可识别的语义即可,并能够保持整个接口的统一性。

  • 像WebDAV就对HTTP方法进行了扩展,增加了LOCK、UPLOCK等方法。而GitHub的API则支持使用PATCH方法来进行issue的更新,例如:

    PATCH /repos/:owner/:repo/issues/:number
  • 注意 ⚠️ 像PATCH这种不是HTTP标准方法的,服务端需要考虑客户端是否能够支持的问题。

URI设计指导

  • 统一资源接口要求使用标准的HTTP方法对资源进行操作,所以URI只应该来表示资源的名称,而不应该包括资源的操作。

  • 通俗来说,URI不应该使用动作来描述。

3. 资源的表述 (Representation)

  • 确切的说,客户端获取的只是资源的表述而已。资源在外界的具体呈现,可以有多种表述(或成为表现、表示)形式,在客户端和服务端之间传送的也是资源的表述,而不是资源本身。例如文本资源可以采用html、xml、json等格式,图片可以使用PNG或JPG展现出来。

  • 资源的表述包括数据和描述数据的元数据,例如,HTTP头"Content-Type" 就是这样一个元数据属性。

  • 客户端可以通过Accept头请求一种特定格式的表述,服务端则通过Content-Type告诉客户端资源的表述形式。

API版本管理

在URI里边带上版本号:

  • http://api.example.com/1.0/foo

  • http://api.example.com/1.2/foo

  • http://api.example.com/2.0/foo

表述格式区分

使用URI后缀来区分表述格式。当服务器不支持所请求的表述格式,那么应该怎么办?若服务器不支持,它应该返回一个HTTP 406响应,表示拒绝处理该请求。

4. 资源的链接 (Hypermedia)

  • 下面展示的是GitHub获取某个组织下的项目列表的请求,可以看到在响应头里边增加Link头告诉客户端怎么访问下一页和最后一页的记录。而在响应体里边,用url来链接项目所有者和项目地址。

  • 上面的例子展示了如何使用超媒体来增强资源的连通性。很多人在设计RESTful架构时,使用很多时间来寻找漂亮的URI,而忽略了超媒体。所以,应该多花一些时间来给资源的表述提供链接,而不是专注于"资源的CRUD"。

5. 状态的转移 (State Transfer)

  • 无状态通信原则,并不是说客户端应用不能有状态,而是指服务端不应该保存客户端状态。

应用状态与资源状态

  • 状态应该区分应用状态和资源状态,客户端负责维护应用状态,而服务端维护资源状态。

  • 但有时候我们会做出违反无状态通信原则的设计,例如利用Cookie跟踪某个服务端会话状态,常见的像J2EE里边的JSESSIONID。

  • 这意味着,浏览器随各次请求发出去的Cookie是被用于构建会话状态的。

  • 当然,如果Cookie保存的是一些服务器不依赖于会话状态即可验证的信息(比如认证令牌就是 token),这样的Cookie也是符合REST原则的。

应用状态的转移

  • "会话"状态不是作为资源状态保存在服务端的,而是被客户端作为应用状态进行跟踪的。

RESTful架构与传统架构对比

RESTful架构与传统的RPC、SOAP等方式在理念上有很大的不同。

总结

RESTful API设计是一门艺术,需要在实践中不断完善。遵循本文介绍的设计原则,能够帮助你构建出更加清晰、易用、可扩展的Web API。记住:好的API设计应该是对资源的抽象,而不是对接口的堆砌。

Last updated

Was this helpful?