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是让整个系统活起来的灵魂。

RESTful API 四大原则结构图 资源导向 无状态 统一接口 每个请求独立 无会话依赖 标准方法 + 状态码 一致的行为约定 HATEOAS 核心 + 支撑 + 规范 → 自我驱动的API 原则 支撑 赋能

从上图你可以看到:资源导向是地基,决定了URL怎么设计;无状态和统一接口是支柱,保证了系统的可扩展性和可预测性;而HATEOAS是在这基础上,让你的API能自我发现、自我驱动。

快速自检清单:设计新API前,拿这张图过一遍。URL是名词吗?请求包含所有必需信息吗?HTTP方法和状态码用得对吗?后续操作有没有以链接形式返回?四个都打勾,你的API设计就已经很RESTful了。