第四章 接口设计原则:五大核心约束

说起RESTful接口,很多新人会以为「把URL写成名词、用HTTP方法做CRUD」就算完了。但在我看来,真正的RESTful架构得遵守五个硬约束——统一接口、无状态、可缓存、分层系统、按需代码。这五个东西就是Roy Fielding博士在里定下的规矩,缺一个就不是完整的REST。

我在做第一个水印平台时,一开始压根没在乎这些原则。结果呢?接口越写越乱,缓存不好做,客户端和服务端耦合得死死的。后来痛定思痛,一条一条对规范修,才把系统救回来。今天就跟你聊聊这五个原则,每个我都会结合自己的经历来讲,希望能帮你少踩坑。

4.1 统一接口

统一接口说白了就是:所有资源都通过同一套协议和格式来交互。你想想看,如果有10个接口,每个接口请求方式、参数结构、返回内容都随心情写,那调用方得多崩溃?

我自己习惯的做法是:

  • 资源用名词:/watermarks、/watermarks/{id}
  • 操作靠HTTP方法:GET查、POST增、PUT全覆盖改、PATCH部分改、DELETE删
  • 固定数据格式:所有响应体统一用JSON,错误包也按统一结构(code + message + data)
  • 使用标准超媒体:响应里带上相关链接,让客户端自己发现下一步操作
我遇到过的一个反例:某团队把删除水印做成了 GET /watermarks/delete?id=xxx。结果客户端以为这是幂等查询,刷新页面直接把所有水印全删了……统一接口的意义就在这儿——规约清楚,出事的概率就低。

4.2 无状态

无状态原则要求:服务端不存任何客户端上下文。每次请求必须包含所有必要信息,服务端按请求本身就能独立处理。说白了,服务端就是个「记忆只有7秒」的鱼。

有人会问:「那登录状态怎么办?」嗯,这里要注意——无状态不是说不让你有或Session,而是说这些凭证信息要带在请求里(比如JWT放在Authorization头),服务端不主动维护会话状态。

我曾经接手过一个老项目,用户登录后服务端会把用户角色存到内存里,结果负载均衡一扩容,用户在A机器登录,请求落到B机器就直接返回401。改成无状态后,每次请求都带,服务端只做验证不存状态,问题就没了。

无状态的好处很明显:水平扩展超轻松,任意节点都能处理任意请求;故障恢复也快——挂掉一台,其他节点顶上就行,不用管会话同步。

4.3 可缓存

可缓存原则是说:要在响应中明确标识这个资源能不能缓存、能缓存多久。客户端和中间代理拿到响应后,根据Cache-Control、ETag、Last-Modified等头来决策。

你想想看,水印接口里大量「查询水印列表」「获取水印模板」这类读操作,数据变化频率低,不缓存简直是浪费带宽。我建议:

  • 对GET请求,默认加 Cache-Control: max-age=3600 或更久
  • 对需要实时数据的关键接口,用 Cache-Control: no-cache 配合ETag做协商缓存
  • POST/PUT/DELETE 等写入操作,服务端校验若资源有变,可以主动清除或失效缓存
避坑指南:别缓存带敏感信息的接口——比如水印密钥、用户。响应里可以加 Cache-Control: private 或 no-store 来控制。

4.4 分层系统

分层原则要求:整个系统拆成若干层,每一层只知道自己相邻的层。客户端不必知道身后是有一台服务器,还是有一堆网关、CDN、缓存层、微服务集群。

我做过的一个水印服务架构大概这样:

  1. 客户端层(App/Web)— 只跟API网关通信
  2. API网关层 — 负责鉴权、限流、请求转发、统一日志
  3. 业务服务层 — 处理水印生成、校验、管理等核心逻辑
  4. 持久化层 — 数据库+对象存储

这么分层的好处:哪一层出问题,替换那一层就行,其他层不用改。比如我要把数据库从MySQL换成PostgreSQL,业务服务层的接口不变,上层完全感知不到。

但注意——分层不能滥用。我见过有人分了七八层,每层只传个参数,导致一次请求产生十几倍网络开销。分层的度很重要。

4.5 按需代码

按需代码是唯一的可选原则。它允许服务端在返回响应时,顺带发送一段可执行的脚本(比如JavaScript),客户端拿到后直接运行,以此扩展自身功能。

这个原则在水印接口里用得不多,但也不是没用。举个例子,水印预览功能——服务端返回一个处理图片的小脚本,客户端在浏览器里直接执行,不用刷新页面就能看到水印效果。我在一个移动端项目里就用过类似方式,把水印定位算法以JavaScript片段下发给客户端,客户端实时计算,体验很好。

不过我得说一句:按需代码适合可控的、同构的环境(比如自家App里嵌入WebView)。在开放API里慎用——传来一段不可信代码,安全风险很高。

安全警告:如果决定启用按需代码,务必对下发的脚本做严格校验、签名、长度限制,并且只允许在沙盒环境里执行。否则别人随便往你的接口里塞恶意代码,后果不堪设想。

五大原则关系图

RESTful 五大接口设计原则关系图 RESTful API 统一接口 无状态 可缓存 分层系统 按需代码 所有原则共同支撑 RESTful 架构风格

从SVG图能看到,RESTful API是中心,五个原则像五个支柱一起支撑着它。每个原则都不孤立,比如可缓存往往依赖统一接口(统一的响应头格式),分层系统需要无状态才能水平伸缩。

各原则速览对比

原则 核心要求 对水印接口的意义 我常用的实践
统一接口 资源命名+HTTP方法+数据格式标准化 客户端无需适配不同写法 统一错误格式、超媒体链接
无状态 每次请求自包含,服务端不存上下文 方便容器化部署、弹性伸缩 JWT传身份,不依赖Session
可缓存 明确响应可缓存性及有效期 减少水印列表/模板重复查询的带宽 Cache-Control + ETag 配合
分层系统 每层只知相邻层,组件可独立替换 业务层与存储分离,升级数据库不影响上层 API网关 → 业务服务 → 存储
按需代码 (可选)服务端下发可执行脚本 水印预览、实时渲染可以下放 JS脚本+沙盒执行,严格校验

总结一下:这五个原则里,前四个是强制约束,第五个按需代码是可选项。但不管是强制的还是可选的,它们共同构成了RESTful架构的规矩。遵守了,你的接口就经得起大规模、高并发的考验;不遵守,短期看可能「快」,长期就是一座屎山。

我个人建议,在设计水印接口的一开始,就把这五个原则写在规范文档最前面。每写一个接口、每加一个功能,就问自己:「这个设计违反了什么原则?」坚持下来,你会感谢自己。