6.1 RESTful API设计原则


文档摘要

6.1 RESTful API 设计原则 RESTful 设计的三根支柱:URL 建模资源(名词),HTTP 方法表达操作语义(GET 安全、PUT 与 DELETE 幂等、POST 两者皆非),状态码报告结果(成功族、客户端错族、服务端错族)。三者一致时,接口无需文档也能被猜中——这正是可预测接口的全部秘密。 接口通道的第一课不写代码,立设计观。观察哨见过的接口设计问题,八成不是技术缺陷,是语义混乱:URL 里塞动词(getOrder)、该 201 的地方回 200、PUT 做成了部分更新。本节把三根支柱各配一张速查表,最后给一份评审清单——它能拦下大部分设计事故。

6.1 RESTful API 设计原则

RESTful 设计的三根支柱:URL 建模资源(名词),HTTP 方法表达操作语义(GET 安全、PUT 与 DELETE 幂等、POST 两者皆非),状态码报告结果(成功族、客户端错族、服务端错族)。三者一致时,接口无需文档也能被猜中——这正是可预测接口的全部秘密。

接口通道的第一课不写代码,立设计观。观察哨见过的接口设计问题,八成不是技术缺陷,是语义混乱:URL 里塞动词(getOrder)、该 201 的地方回 200、PUT 做成了部分更新。本节把三根支柱各配一张速查表,最后给一份评审清单——它能拦下大部分设计事故。

资源建模:URL 是名词不是句子

设计对比(同一功能两种风格): 动词风格 POST /getOrderList ← URL 在下指令,方法被架空 资源风格 GET /orders ← URL 指明资源,方法说明意图 常见资源形态: GET /orders 订单集合 GET /orders/42 单个订单 POST /orders 在集合里创建订单 PUT /orders/42 整体替换订单 42 PATCH /orders/42 部分更新订单 42 DELETE /orders/42 删除订单 42 GET /customers/7/orders 客户 7 的订单(嵌套限定范围)

资源建模的两条经验:层级表达从属(客户下的订单),两层为限——再深说明中间资源该独立建模了;过滤、排序、分页是集合的参数不是路径(问号后的 page、size、q),路径段留给身份。

方法语义:安全与幂等两张底牌

方法 安全 幂等 语义要点 典型误用
GET 只读,不改任何状态 GET 里顺手改了计数
POST 创建、或非幂等的触发动作 重试导致重复下单
PUT 整体替换,重复执行结果一致 做成部分更新(该用 PATCH)
PATCH 否* 部分更新(实现保证幂等更佳) 与 PUT 混用无标准
DELETE 删除,重复删除同一资源结果一致 删除后第二次返回 500

安全与幂等不是道德要求,是契约:客户端与中间件据此决定能否重试、能否缓存。POST 不幂等意味着"网络超时后客户端不敢重试"——订单接口要自己准备幂等键(客户端生成唯一标识,服务端去重),这是电商接口的标配设计。

状态码:结果的语言

2xx 成功族: 200 OK 通用成功(GET 带正文、PUT 更新成功) 201 Created 创建成功,Location 头指明新资源地址 204 No Content 成功但无正文(DELETE、PUT 后的省流式) 4xx 客户端错族(客户端改请求可解决): 400 Bad Request 请求格式或语义错误(验证失败的标准码) 401 Unauthorized 未认证:不知道你是谁 403 Forbidden 已认证但无权限:知道你是谁,不许进 404 Not Found 资源不存在(也可用于隐藏无权访问的资源) 409 Conflict 状态冲突(并发覆盖、重复创建) 5xx 服务端错族(服务端背锅): 500 Internal Error 未处理异常的兜底 503 Unavailable 服务暂不可用(过载、维护)

401 与 403 的区分值得单独记:401 是"没刷卡",403 是"刷了卡但没权限"。把语义用对,客户端才能写出正确的处理分支(跳登录页还是提示无权限)——第 7 章的认证授权站会精确产出这两个码。

图 6.1-1 接口设计三层速查矩阵

图 6.1-1 接口设计三层速查矩阵

案例:把一个"动词式"接口改造成资源式

背景:接手一个遗留接口,创建订单的调用方式是 POST 加动作路径,响应永远 200,成功失败靠响应体里的自定义代码区分。移动端同事抱怨"每次都要解析正文才知道结果",网关也无法按状态码做限流统计。

操作:三步改造。第一步资源化路径:动作路径换成资源集合;第二步归还语义:创建成功改返回 201 并带 Location,验证失败返回 400 加标准错误体;第三步补幂等键:请求头带客户端生成的唯一键,服务端记住键与结果,重复提交直接返回首次结果。

[ApiController] [Route("api/orders")] public class OrdersApiController : ControllerBase { [HttpPost] [ProducesResponseType(typeof(OrderView), StatusCodes.Status201Created)] [ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status409Conflict)] public async Task<ActionResult<OrderView>> Create( OrderCreateInput input, // FromBody 自动推断 [FromHeader(Name = "Idempotency-Key")] string? idemKey) { if (await _orders.ExistsAsync(input.ExternalNo)) return Conflict(new ProblemDetails // 409:重复创建有标准错误体 { Title = "订单号已存在", Detail = $"外部单号 {input.ExternalNo} 已创建" }); var order = await _orders.CreateAsync(input); // 201 + Location:客户端拿到新资源的确定地址 return CreatedAtAction(nameof(GetById), new { id = order.Id }, order.ToView()); } [HttpGet("{id:int}")] public async Task<ActionResult<OrderView>> GetById(int id) { var order = await _orders.FindAsync(id); return order is null ? NotFound() : Ok(order.ToView()); } }

结果:改造后接口行为对齐三支柱——URL 表资源、POST 语义配幂等键、状态码分层。网关按 4xx 与 5xx 比例出监控大盘,移动端删掉了"解析正文判断成败"的私有协议代码。

解读:这轮改造没有引入任何新框架,全部收益来自"把 HTTP 本身的语义用对"。网关、缓存、重试组件都默认你遵守契约——状态码就是它们的世界语。

变式:对无法完全幂等的复杂动作(审核、取消),资源化思路是把它建模成子资源的状态变更(POST 一个 cancellation 资源),或退一步用动作子路径并诚实标注非幂等——设计原则服务沟通,不是教条。

⚠️ 常见坑:把 REST 当勋章,为"纯度"牺牲可用性。批量操作、复杂查询、长任务(导入导出)都不必硬塞 CRUD 模型——建模成任务资源(创建任务、轮询状态)比扭曲语义更 REST。原则的目的是让接口可预测,可预测才是唯一的验收标准。

💡 关键直觉:把接口想象成自动售货机的键盘——键位(URL)标货物、按键方式(方法)表意图、指示灯(状态码)报结果。三个层面都符合直觉时,用户不用读说明书。

本节要点回顾

  • URL 表资源:名词复数、两层层级、参数走查询串,动词出现即警讯;
  • 方法底牌:GET 安全、PUT 与 DELETE 幂等、POST 需要幂等键兜底重试;
  • 状态码是契约:201 带 Location、401 与 403 分家、409 表冲突,别全 200;
  • 三层自检:能否猜资源、重放是否一致、按码能否分流——评审清单就这三问;
  • 原则服务可预测:批量与长任务建模成资源,不为纯度牺牲可用性。

作者与出处
原作者: 灏天文库
来源:灏天文库
整理: 灏天文库整理
由灏天文库平台收录,内容或由平台用户上传,仅供学习交流
发布者: 作者: 灏天文库 转发
评论区 (0)
U