3. 接口设计原则回顾:RESTful API 设计规范,单一职责原则在接口中的应用,接口版本管理策略

说实话,我见过太多团队在接口设计上栽跟头了。有的接口参数混乱,有的一个接口干三件事,还有的版本管理全靠口头约定。今天咱们就把这些坑一个个填上。

3.1 RESTful API 设计规范

RESTful 不是什么高深理论,说白了就是一套约定俗成的接口设计规矩。我刚开始写接口时也觉得这玩意儿麻烦,直到接手一个全是自定义动词的项目……嗯,那酸爽,至今难忘。

3.1.1 资源命名与 HTTP 动词

核心思想就一句话:用名词表示资源,用动词表示操作。来看个对比:

错误做法 正确做法 说明
GET /getUser GET /users/{id} 动词交给 HTTP 方法
POST /createOrder POST /orders 创建用 POST
GET /deleteProduct?id=1 DELETE /products/1 删除用 DELETE
POST /updateUserStatus PATCH /users/{id}/status 部分更新用 PATCH

关键原则:URL 中只出现名词复数形式,操作语义由 HTTP 方法表达。我见过一个项目,所有接口都用 POST,结果排查问题时根本分不清哪个是查询哪个是修改。

3.1.2 状态码的正确使用

很多新手喜欢所有成功都返回 200,所有失败都返回 500。这其实是在给自己挖坑。我个人习惯这样分配:

  • 200:GET 成功、PUT 成功
  • 201:POST 创建成功
  • 204:DELETE 成功(无返回体)
  • 400:参数校验失败
  • 401:未认证
  • 403:无权限
  • 404:资源不存在
  • 409:资源冲突(比如重复创建)
  • 422:业务逻辑校验失败
  • 429:限流触发
  • 500:服务端内部错误

小技巧:我曾经在项目中统一封装了响应体,把业务错误码放在 body 里,HTTP 状态码只反映传输层状态。这样前端可以根据业务码做精细化处理,而不是看到 500 就弹「系统异常」。

3.2 单一职责原则在接口中的应用

单一职责原则(SRP)不只是类设计的事,接口同样适用。一个接口只做一件事,并且做好。你想想看,如果一个接口既查询用户信息又更新用户积分,那它出了问题你都不知道是查挂了还是改挂了。

3.2.1 接口粒度的把控

我遇到过最夸张的一个接口,参数有 20 多个,返回字段 50 多个,里面同时做了订单查询、库存扣减、积分累计、消息推送……这哪是接口,分明是个定时炸弹。

正确的做法是:

  • 查询接口:只做查询,不做修改
  • 命令接口:只做修改,不做查询
  • 批量接口:明确语义,比如 POST /orders/batch
  • 状态变更接口:用 PATCH 只更新变更字段
// ❌ 错误:一个接口干三件事
POST /api/order/process
{
  "orderId": 123,
  "action": "pay_and_ship_and_notify"
}

// ✅ 正确:拆分为三个独立接口
POST /api/orders/123/pay
POST /api/orders/123/ship
POST /api/orders/123/notify

避坑指南:我曾经把一个「创建订单并发送通知」的接口写在一起,结果通知服务挂了,订单也创建失败。后来拆成两步:先创建订单(返回订单 ID),再异步发送通知。这样即使通知失败,订单数据也不会丢失。

3.2.2 参数与返回值的单一职责

接口的参数和返回值也要遵循单一职责。一个接口的参数应该只包含它需要的数据,不要为了「方便」把整个对象传进去。

// ❌ 错误:传了整个用户对象,但只需要 userId
POST /api/orders
{
  "user": {
    "id": 1,
    "name": "张三",
    "email": "zhang@example.com",
    "phone": "13800138000"
  },
  "productId": 100
}

// ✅ 正确:只传需要的字段
POST /api/orders
{
  "userId": 1,
  "productId": 100
}

3.3 接口版本管理策略

接口版本管理是个老生常谈的话题,但真正做好的团队不多。我见过最野的路子是在接口文档里写「此接口已废弃,请使用新接口」,然后就没有然后了。

3.3.1 三种主流版本策略

策略 示例 优点 缺点
URL 路径版本 /api/v1/users 直观、易路由 URL 冗余
请求头版本 Accept: application/vnd.myapp.v2+json URL 干净 调试不方便
查询参数版本 /api/users?version=2 实现简单 容易被缓存污染

我个人习惯用 URL 路径版本。为什么?因为直观。你打开浏览器就能看到版本号,不用翻请求头。而且网关做路由分发也方便。

3.3.2 版本的生命周期管理

版本不是发布了就完事了,得有退出机制。我一般这样管理:

  • v1:当前稳定版,全量使用
  • v2:新功能开发中,灰度发布
  • v1-deprecated:标记废弃,保留 3 个月
  • v1-removed:彻底下线,返回 410 Gone

实践经验:我在上一个项目中,每次发布新版本都会在响应头加一个 X-API-Version: v2 和 X-API-Deprecated: true。客户端看到废弃标记后,有足够时间迁移。3 个月后,我们统计调用量,确认没有流量了再下线。

3.3.3 向后兼容的底线

版本升级时,有些东西绝对不能改:

  • 不能删字段:老客户端可能还在用
  • 不能改字段类型:比如把 int 改成 string,解析会崩
  • 不能改枚举值:新增可以,删除不行
  • 不能改接口语义:GET 不能突然变成写操作

血的教训:我曾经把一个接口的返回字段 userName 改成了 name,觉得「反正前端也会改」。结果有 3 个老版本客户端没更新,线上直接白屏。从那以后,我所有接口都加了字段映射层,新旧字段同时返回,等确认旧字段没人用了再删。

3.4 本章知识体系

下面这张图把本章的核心知识点串起来了,你可以对照着回顾:

接口设计核心知识体系 RESTful 设计规范 单一职责原则 版本管理策略 资源命名 + HTTP 动词 状态码语义化 统一响应体结构 查询与命令分离 参数最小化原则 返回值精简 URL 路径 / 请求头 / 参数 生命周期管理 向后兼容底线 核心目标:可维护、可扩展、可兼容 每个接口只做一件事,每个版本都有退出计划

这三块内容看似独立,其实环环相扣。RESTful 规范给你定骨架,单一职责帮你定粒度,版本管理给你留退路。少了任何一个,接口设计都会出问题。