2. RESTful API 设计原则:资源导向、无状态、统一接口、HATEOAS简介
聊到RESTful API,很多人觉得不就是“URL写得好看点”吗?其实没那么简单。我做了这么多年API设计,真正吃透这几个原则的项目,维护成本能降一个数量级。今天我把最核心的四个原则掰开揉碎讲清楚。
这四个原则不是独立存在的。它们像一套组合拳,打好了你的API就会很“RESTful”。说白了,就是让别人用起来舒服,你自己改起来也轻松。
2.1 资源导向:把一切都看作资源
这可能是RESTful设计里最基础,也最容易被忽略的一条。什么是资源?用户、订单、商品、文章……你的业务里任何一个“东西”,都是资源。
核心思想:每个URL代表一个资源,而不是一个操作。
我见过太多接口写成这样:/getUser、/createOrder、/deleteArticle。这种设计,说白了就是把API当RPC在写。你想想看,如果每个操作都得发明一个新URL,那项目大了以后,光维护URL列表就够你受的。
正确的做法:用名词复数表示资源集合,用路径层级表示资源关系。
GET /users—— 获取用户列表GET /users/42—— 获取ID为42的用户POST /users—— 创建一个新用户PUT /users/42—— 完全替换ID为42的用户PATCH /users/42—— 部分更新ID为42的用户DELETE /users/42—— 删除ID为42的用户
我在项目中遇到过团队用动词URL的情况,理由是“这样更直观”。嗯,短期看确实好理解。但三个月后新同事接手,对着几十个动词URL一脸懵,根本分不清哪些是读操作哪些是写操作。一条原则省去一堆沟通成本,我觉得很值。
关键设计细节:
- URL只用名词,不用动词
- 用复数形式表示集合(
/users而不是/user) - 用路径表示从属关系(
/users/42/orders表示42号用户的订单) - 不要用?号传资源ID(
/users?id=42是不推荐的)
2.2 无状态:每个请求都自给自足
无状态,听起来很抽象。说白了就是服务器不记得你是谁。每个请求都得带上所有需要的信息,包括认证信息、上下文数据等。
为什么会这样?你想想看,如果服务器要保存客户端的状态,那出了问题谁负责?服务器挂了怎么办?水平扩展的时候得复制所有会话状态,那得多麻烦?
我的习惯:用JWT(JSON Web )在客户端保存用户会话信息。每个请求在请求头里带上这个,服务器校验后就知道你是谁、有什么权限。不需要在服务端存任何会话数据。
我曾经在一个项目里因为没遵循无状态吃了大亏。当时为了让客户端少传一些参数,我把用户购物车信息存在服务端session里。结果一上负载均衡,用户请求被分到不同服务器,购物车内容时有时无。客户投诉电话都打爆了……从那以后,我再也不敢在服务端存客户端状态了。
无状态的好处:
- 易于扩展:任何服务器都可以处理任何请求,水平扩展毫无压力
- 可靠:服务器重启不会丢失客户端数据
- 可见性:每个请求都是完整的,监控和调试更方便
- 缓存友好:没有状态依赖,缓存策略更简单
当然,无状态也有代价。每个请求都得带上认证信息,数据量会比session大一些。但相比带来的扩展性和可靠性,这点代价我觉得完全可以接受。
2.3 统一接口:用标准方式做标准事
统一接口,意味着所有资源的操作方式都保持一致。这是RESTful API能被广泛采用的关键原因——你学会了一个资源的CRUD,就学会了所有资源的CRUD。
统一接口的四大约束:
| 约束 | 说明 | 示例 |
|---|---|---|
| 资源标识 | 每个资源都有一个唯一的URL | /users/42 |
| 通过表述操作资源 | 客户端通过资源表述(如JSON)来操作资源,而不是直接调用方法 | POST一个JSON对象来创建资源 |
| 自描述消息 | 每个消息都包含足够信息描述如何处理它(HTTP方法、媒体类型、状态码) | Content-Type: application/json |
| HATEOAS | 响应中包含可执行动作的链接,指导客户端下一步 | 订单创建后返回支付链接 |
我这里重点讲前三条,第四条我们下一节详细展开。
标准HTTP方法:
GET—— 读取资源(幂等、安全)POST—— 创建资源(非幂等)PUT—— 完全替换资源(幂等)PATCH—— 部分更新资源(非幂等)DELETE—— 删除资源(幂等)
注意:不要滥用POST。我见过有些团队所有操作都用POST,理由是“前端方便”。这种偷懒会造成接口含义模糊,别人根本搞不清你这个POST到底是创建还是更新,是安全操作还是非安全操作。统一接口的意义,就是让你的接口行为可预测。
标准状态码:
200 OK—— GET/PUT/PATCH成功201 Created—— POST成功创建资源204 No Content—— DELETE成功400 Bad Request—— 客户端请求错误401 Unauthorized—— 未认证403 Forbidden—— 无权限404 Not Found—— 资源不存在409 Conflict—— 资源冲突(如重复创建)500 Internal Server Error—— 服务器内部错误
讲真,把HTTP方法和状态码用对,你接口的可读性已经提升了80%。剩下的20%就是下面要说的。
2.4 HATEOAS:让API自我指引
HATEOAS,全称是Hypermedia as the Engine of Application State。名字很唬人,但其实核心思想很简单:API响应里,带上下一步可以做什么的链接。
我记得第一次接触这个概念是在做支付系统的时候。用户订单创建成功后,响应里直接返回了支付链接。前端拿到这个链接就知道接下来该去哪里完成支付,根本不需要在代码里写死跳转逻辑。这就是HATEOAS的活学活用。
看个例子:
{
"orderId": 12345,
"status": "pending",
"totalAmount": 299.00,
"links": {
"self": "/orders/12345",
"payment": "/orders/12345/payment",
"cancel": "/orders/12345/cancel"
}
}
传统的响应只返回数据,客户端要自己去拼支付URL。有了HATEOAS,客户端直接认links.payment就行。服务器改了URL结构,客户端根本不需要改。
HATEOAS的核心优势:
- 解耦:客户端不硬编码URL,服务器可以灵活调整路径
- 自发现:新来的开发者看一个响应就知道整个API怎么用
- 版本演进:通过链接变更就能实现接口的渐进式升级
当然,不是所有场景都需要完整的HATEOAS。我建议从核心业务流程开始用,比如订单、支付、用户认证这些链路复杂的地方。简单的CRUD接口,带上self link就够了。
SVG:RESTful四大原则结构图
为了让你一眼看清这四个原则的关系,我画了一张结构图。资源导向是核心,无状态和统一接口是支撑,HATEOAS是让整个系统活起来的灵魂。
从上图你可以看到:资源导向是地基,决定了URL怎么设计;无状态和统一接口是支柱,保证了系统的可扩展性和可预测性;而HATEOAS是在这基础上,让你的API能自我发现、自我驱动。
快速自检清单:设计新API前,拿这张图过一遍。URL是名词吗?请求包含所有必需信息吗?HTTP方法和状态码用得对吗?后续操作有没有以链接形式返回?四个都打勾,你的API设计就已经很RESTful了。