RESTful API 的设计原则
什么是 REST?—— 理念与起源
首先,我们必须明确:REST (Representational State Transfer,表述性状态转移) 是一种软件架构风格,而不是一个标准或协议。它由 Roy Fielding 博士在 2000 年的博士论文中提出,旨在为分布式超媒体系统(如万维网)设计一套简洁、清晰的约束条件。
你可以把它理解为一套“设计哲学”或“最佳实践的集合”。如果一个 API 遵循了 REST 架构风格,我们就可以称它为 RESTful API。
它的核心思想是:将服务器上的数据或功能抽象为“资源”(Resource),客户端通过操作资源的“表征”(Representation)来改变其状态。
- 资源:可以是任何事物,如一个用户、一篇文章、一张图片,甚至是一种服务(如“发送邮件”)。每个资源都有一个唯一的标识符,即 URI (Uniform Resource Identifier)。
- 表征:是资源在特定时刻的状态的表述。它通常是 JSON 或 XML 格式,但也可以是 HTML、纯文本或图片等。客户端通过操作(如获取、修改、删除)资源的表征来与服务器交互。
RESTful API 的核心设计原则(六大约束)
资源标识(Resource Identification)
REST 的核心是 “资源”(如用户、订单、商品),需通过唯一 URI(Uniform Resource Identifier) 对资源进行标识,而非直接标识 “操作”。
设计要求:
- URI 以资源名词为核心:使用复数名词(体现资源集合特性),避免动词(如/users而非/getUsers)。
- 层级清晰:通过 URI 路径体现资源间的关联关系(如 “某个用户的订单”)。
- 避免冗余:不包含 HTTP 方法(如/deleteUser/123错误,应通过DELETE /users/123实现)。
示例:
| 场景 | 正确 URI | 错误 URI | 原因分析 |
|---|---|---|---|
| 用户资源集合 | /users | /getAllUsers | 错误使用动词,违背 “资源标识” |
| 单个用户(ID=123) | /users/123 | /user?id=123 | 未通过路径明确资源唯一性 |
| 用户 123 的订单集合 | /users/123/orders | /getUserOrders?uid=123 | 未体现资源层级关联 |
统一接口(Uniform Interface)
通过标准化的 HTTP 方法 + 资源表述实现客户端与服务器的交互,降低耦合度。这是 RESTful API 最关键的原则,包含 4 个子约束:
- 资源表述(Resource Representation)
- 服务器返回 “资源的表述”(如 JSON、XML),而非资源本身。客户端通过表述操作资源,无需了解服务器内部实现。
- 示例:请求GET /users/123,服务器返回 JSON 格式的用户信息(表述),而非数据库中的原始数据。
- 自描述消息(Self-Descriptive Messages)
- 消息需包含足够信息,让客户端理解如何处理(如数据格式、缓存策略)。
- 依赖 HTTP 头部实现:
- Content-Type:声明响应数据格式(如application/json);
- Cache-Control:指定缓存规则(如max-age=3600表示缓存 1 小时);
- Accept:客户端告知服务器可接受的格式(如Accept: application/json)。
- 超媒体作为应用状态引擎(HATEOAS)
- 响应中包含 “链接”(Hyperlinks),客户端通过链接导航到其他资源,无需硬编码 URI(类似网页通过链接跳转),实现 “无状态交互”。
- 示例(用户详情响应):
{ "id": 123, "name": "Alice", "email": "alice@example.com", "_links": { "self": {"href": "/users/123"}, // 当前资源链接 "orders": {"href": "/users/123/orders"}, // 关联的订单资源链接 "edit": {"href": "/users/123", "method": "PUT"} // 修改资源的操作链接 } }
- 标准 HTTP 方法映射 CRUD 操作
- 复用 HTTP 协议原生方法,明确操作语义,避免自定义动词。常用映射关系如下:
HTTP 方法 操作语义 作用于资源集合(如/users) 作用于单个资源(如/users/123) 幂等性 * 安全性 ** GET 查询(Read) 获取所有用户列表 获取 ID=123 的用户详情 ✅ ✅ POST 创建(Create) 新增一个用户(服务器分配 ID) 不推荐(应作用于集合) ❌ ❌ PUT 全量更新(Update) 不推荐(集合无 “全量更新” 语义) 覆盖更新 ID=123 的用户所有字段 ✅ ❌ PATCH 部分更新(Update) 不推荐 仅更新 ID=123 的用户部分字段(如邮箱) ✅ ❌ DELETE 删除(Delete) 不推荐(风险高,需谨慎设计) 删除 ID=123 的用户 ✅ ❌
- 复用 HTTP 协议原生方法,明确操作语义,避免自定义动词。常用映射关系如下:
无状态(Stateless)
- 这是最重要的原则之一。每一次从客户端到服务器的请求都必须包含理解该请求所需的所有信息。服务器不应存储任何客户端请求之间的上下文状态。
- 会话状态(Session State)应完全保存在客户端(例如通过 Token、API Key 或在请求体中传递必要信息)。
- 好处:
- 可扩展性:服务器不需要维护状态,可以轻松地添加更多服务器来处理请求。
- 可靠性:请求失败后更容易恢复,因为所有信息都包含在一次请求中。
- 可见性:监控系统无需查看多个请求就能理解单个请求。
- 示例:客户端请求GET /users/123时,需在 HTTP 头部携带Authorization: Bearer {token},服务器通过 Token 验证身份,而非依赖 “会话 ID”。
缓存(Cacheable)
- 服务器响应必须明确指示其是否可缓存,以及可以缓存多久。这可以显著减少客户端-服务器交互,提高性能。
- 通常通过 HTTP 头如 Cache-Control, Expires, ETag 等来实现。
分层系统(Layered System)
- 系统可以由多个层次组成(如负载均衡器、安全层、应用服务器、数据库),每层仅与相邻层交互,客户端无法也无须知道它是直接与终端服务器通信,还是与中间的某层通信。
- 优势:
- 隔离关注点(如网关负责认证、限流,应用层负责业务逻辑);
- 可灵活扩展(如新增缓存层提升性能,新增监控层收集日志,不影响客户端和应用层)。
按需代码(Code on Demand,可选)
- 这是唯一的可选原则:服务器可临时向客户端发送可执行代码(如 JavaScript、Java Applet),扩展客户端功能(如动态渲染表单、处理复杂逻辑)。
- 注意:需权衡安全性(避免恶意代码)和复杂度,目前 RESTful API 中较少使用(更常见于前端与后端的交互,如 SPA 应用加载后端返回的脚本)。
总结:RESTful API 设计的核心目标
通过上述原则,最终实现 API 的 “可预测性、可扩展性、可维护性”:
- 可预测性:开发者通过 URI 和 HTTP 方法即可明确 API 语义(如看到GET /users/123就知道是 “查询 ID=123 的用户”);
- 可扩展性:无状态、分层设计支持服务器水平扩展,应对高并发;
- 可维护性:统一接口减少个性化设计,降低团队协作成本。
实际设计中,需灵活平衡原则与业务场景(如 HATEOAS 对简单 API 可能过度复杂,可简化实现),但核心的 “资源标识、HTTP 方法复用、无状态” 是必须遵守的基础。