2. RESTful API 设计原则:REST 架构风格与资源导向设计
说实话,我刚开始学REST时,以为它就是个URL命名规范——把动词换成名词、用个斜杠就行了。后来自己写了一套给令牌化服务用的接口,上线第二天就被后端同事找上门,说“你这接口用起来跟RPC有啥区别?” 嗯,从那以后我才认真去读Fielding的,才明白REST是一组架构约束,不是模板。今天我们就聊聊这组约束,以及“资源导向设计”到底怎么落地。
2.1 什么是REST?
REST(Representational State Transfer)是Roy Fielding博士在2000年提出的架构风格。它定义了六项约束,只有全部满足才能称为RESTful。说白了,它就是一套让分布式系统更松耦合、更可缓存的规则。
- 客户端-服务器:分离关注点,客户端不关心数据存储,服务器不关心UI状态。
- 无状态:每个请求包含所有必要信息,服务器不保存客户端上下文。我踩过这个坑,刚开始把用户会话塞到服务端内存里,结果扩缩容时session全丢了。
- 缓存:响应可被隐式或显式标记为可缓存,减少网络开销。
- 统一接口:这是REST的核心特征,包括资源标识、通过表示操作资源、自描述消息、HATEOAS(超媒体作为应用状态引擎)。
- 分层系统:中间层(如反向代理、缓存)对客户端透明。
- 按需代码(可选):服务器可以临时向客户端传递脚本,比如小程序逻辑。
2.2 资源导向设计
资源导向(Resource-Oriented)就是把所有业务实体都抽象为“资源”。每个资源有一个唯一的URI,通过HTTP方法来表达操作意图。你想想看,如果资源是“订单”,那URI就是/orders/{id},获取用GET,创建用POST,替换用PUT,部分更新用PATCH,删除用DELETE。
我建议大家遵循几条命名惯例:
- 使用名词复数:
/tokens而不是/getToken - 路径层级表达从属关系:
/customers/{id}/cards表示某个客户下的卡片 - 查询参数用来过滤、分页:
?status=active&page=2 - 避免将动作塞进URI:POST
/tokens+ body 中的action: detokenize好于/detokenize
POST /tokens/tokenize 和 POST /tokens/detokenize? 不对——这又回到RPC了。更REST的做法是:POST /tokens(创建令牌)和 POST /tokens/{}/detokenize(对特定令牌执行反令牌化,把反令牌化看作一种动作资源)。不过实践中很多团队为了清晰,还是会用自定义action。关键是你得知道理想态是什么。
2.3 传统RPC与REST的对比
很多人在设计API时不自觉就写成RPC风格。下面这张表能帮你快速区分:
| 维度 | RPC风格 | REST风格 |
|---|---|---|
| URI设计 | 动作路径,如 /getUser?id=1 | 资源路径,如 /users/1 |
| HTTP方法 | 通常只用GET或POST | 使用全部语义(GET/POST/PUT/PATCH/DELETE) |
| 状态管理 | 依赖服务端会话 | 无状态,每个请求自包含 |
| 缓存能力 | 弱,因为相同URL可能返回不同数据 | 强,可充分利用HTTP缓存机制 |
| 耦合性 | 客户端与服务端绑定接口签名 | 客户端通过媒体类型和链接解耦 |
| 典型示例 | POST /api?method=createToken |
POST /tokens(创建令牌资源) |
记住一点:REST不是HTTP的CRUD,而是通过资源的表述来驱动状态转移。你发出一个GET请求收到JSON,JSON里可能包含超链接(HATEOAS),客户端跟着链接走到下一个状态——这才是REST想表达的东西。
2.4 令牌化服务中的资源设计实例
我们拿令牌化服务举例。假设业务需要:生成令牌、通过令牌获取明文(反令牌化)、查询令牌状态、删除令牌。
- 令牌集合:
GET /v1/tokens→ 获取令牌列表(分页) - 创建令牌:
POST /v1/tokens→ body携带明文数据,返回令牌 - 获取单个令牌:
GET /v1/tokens/{id}→ 但注意,出于安全通常不允许直接获取明文,所以返回的表示只包含元数据,不包含明文。反令牌化应该走单独接口 - 反令牌化:
POST /v1/tokens/{id}/detokenize→ 看作一个动作资源,body传入权限凭证,返回明文 - 删除令牌:
DELETE /v1/tokens/{id}
POST /v1/detokenize,请求体里同时带令牌ID和明文。结果上线后运维投诉:日志里全是不相关的请求,无法通过URL做权限控制。后来改成POST /v1/tokens/{id}/detokenize,URL就能精确匹配到某个令牌,权限模型也清晰了。这就是资源导向设计的好处——路径本身就是业务语义。
下面这张图展示了REST架构的核心概念如何在令牌化服务中流转:
看这张图,客户端发出一个HTTP请求指向/v1/tokens/abc123(URI),服务端返回一个JSON表示。这个JSON里可能包含_links字段,指向可执行的操作(如“反令牌化”的URL)。客户端通过这个链接拿到明文,这就是一次状态转移。整个过程服务器不保存客户端会话,所有状态都存在于资源和表示中。
POST /batch-create-tokens。正确的思路是:POST /tokens/batch? 或者用资源集合的子资源。我倾向于POST /tokens/batch,把batch看作一个动作资源。但更纯粹的做法是用POST /tokens并允许body传入数组。你觉得哪种更适合你的团队? 嗯,没有标准答案,但理解原则后你可以做出有依据的选择。