RESTful API 设计原则
说到 API 设计,你可能觉得不就是写几个接口吗?其实没那么简单。我见过不少团队,接口设计随心所欲,结果联调时鸡飞狗跳。RESTful 风格不是银弹,但确实能帮你省掉很多沟通成本。
这一节,我们聊聊 RESTful API 的几个核心点。资源命名、HTTP 动词、状态码、版本管理,还有文档生成。每个点我都踩过坑,咱们一个一个拆开说。
资源命名:用名词,别用动词
REST 的核心是资源。资源就是名词,不是动作。你看很多老系统,接口长这样:/getUserInfo、/createOrder。这其实不是 RESTful。
RESTful 的思路,是把所有东西都当成资源。用户、订单、模型、任务,都是资源。用名词复数表示资源集合,用路径层级表示从属关系。
举个例子。我有一个模型推理服务,资源设计可以这样:
// 模型资源
GET /models // 获取模型列表
POST /models // 部署新模型
GET /models/{model_id} // 获取单个模型详情
DELETE /models/{model_id} // 删除模型
// 推理任务资源
POST /models/{model_id}/inferences // 触发推理
GET /inferences/{task_id} // 查询推理结果
注意几个细节。第一,统一用小写连字符,不用下划线。第二,层级不要太深,超过三级就该考虑拆分。我习惯把嵌套限制在三级以内,再深就拆成子资源。
命名的几个硬规矩
- 只用名词复数(/models,/users)
- URI 中用连字符,不用下划线驼峰
- 层级最深不要超三级
- 避免在路径里用动词(别写 /getModel)
HTTP 动词:CRUD 的天然映射
HTTP 动词就是一套标准操作。GET、POST、PUT、PATCH、DELETE,正好对应资源的增删改查。这不是我发明的,但很多团队没用好。
看看这张对应关系:
| HTTP 动词 | 操作 | 是否幂等 | 典型场景 |
|---|---|---|---|
| GET | 查询 | 是 | 获取资源 |
| POST | 创建 | 否 | 新建模型 |
| PUT | 全量更新 | 是 | 替换整个资源 |
| PATCH | 部分更新 | 否 | 修改模型配置 |
| DELETE | 删除 | 是 | 移除资源 |
这里有个常见误区:PUT 和 PATCH 的区别。我见过不少团队全部用 PUT,结果部分更新时传入完整对象,既浪费带宽又容易出错。说实话,PATCH 才是部分更新的正确选择。
我个人的习惯
对于批量操作,用 POST 带上一个 action 参数。比如批量删除:
POST /models/batch-delete
{ "model_ids": ["m1","m2","m3"] }
虽然不符合纯 REST 规范,但实践中更直观。
状态码:别只返回 200 和 500
很多项目接口返回永远只有 200 和 500。你想想看,前端拿到 200 还要去看 body 里到底是成功还是失败,这不是浪费吗?
HTTP 状态码本身就是信号,咱得用起来。
| 范围 | 含义 | 常用码 | 说明 |
|---|---|---|---|
| 2xx | 成功 | 200 201 204 | 200 正常,201 创建成功,204 删除成功 |
| 3xx | 重定向 | 301 302 307 | 很少在 API 中使用 |
| 4xx | 客户端错误 | 400 401 403 404 | 400 参数错,401 未认证,403 无权限,404 资源不存在 |
| 5xx | 服务端错误 | 500 502 503 | 500 内部错,502 网关错,503 服务不可用 |
我记得有一次排查线上问题,对方接口永远返回 200,结果 body 里用 code 字段区分状态。日志查起来特别费劲。从那以后,我坚持状态码和业务含义一一对应。
当然,状态码也不用全背。记住常用的十个左右就够了。核心原则:2xx 表示请求已处理,4xx 表示你该改代码,5xx 表示我服务器该修了。
曾经踩过的坑
我刚写 API 时,喜欢在 400 里返回各种业务校验错误,比如"密码长度不够"、"邮箱格式错误"。后来发现这样做客户端很难统一处理。现在我习惯:
- 参数格式问题用 400
- 权限问题用 403
- 资源不存在用 404
- 冲突(如重复创建)用 409
这样前端可以根据状态码走不同分支,不用解析 body 里的错误类型。
版本管理:没有版本号,就是给自己挖坑
接口一定会变。加字段、改字段、删字段,总得经历。你不能指望所有客户端同步更新。所以版本号是必需品。
版本管理的常见方式有两种:URI 版本号和请求头版本号。
方式一:URI 版本号
GET /api/v1/models
GET /api/v2/models
POST /api/v1/models
这种方式最直观,我推荐新人用。一目了然,调试方便。
方式二:请求头版本号
GET /api/models
Accept: application/vnd.myapp.v1+json
Accept: application/vnd.myapp.v2+json
请求头方式更符合 REST 哲学,路径干净,适合大型网关场景。但调试麻烦,curl 要加额外头。
我个人习惯,在微服务内部用请求头版本号,对外公开 API 用 URI 版本号。对外接口简单直接最重要。
版本管理原则
- 语义版本控制(MAJOR.MINOR.PATCH)
- 不兼容变更必须升大版本
- 小版本至少兼容 6 个月
- 旧版本标注弃用时间(Deprecation 头)
文档生成:OpenAPI + Swagger 实战
你以为只要做好服务端就得了吗?接口变了,文档没更新,联调时一样会吵架。我见过不少团队盯着源码看接口参数,效率极低。
OpenAPI(原来叫 Swagger)标准现在基本是业界标配。写代码的时候就把文档一起生成了,省去额外维护的精力。
我用 FastAPI 举例,它默认集成 OpenAPI。
from fastapi import FastAPI
from pydantic import BaseModel
app = FastAPI(title="模型推理服务", version="v1")
class ModelConfig(BaseModel):
model_id: str
max_batch: int = 32
gpu_ids: list[int]
@app.post("/models", status_code=201)
async def deploy_model(config: ModelConfig):
"""部署新模型到推理引擎"""
return {"status": "ok", "model_id": config.model_id}
运行后访问 /docs 就能看到交互式 Swagger UI。参数、返回值、状态码、错误说明,全部自动生成。
如果现有项目是 Flask 或 Django,也能通过装饰器方式集成。比如 Flask 项目:
from flask_openapi3 import OpenAPI, Info
from pydantic import BaseModel
info = Info(title="推理服务 API", version="v1")
app = OpenAPI("myapp", info=info)
class QueryModel(BaseModel):
model_id: str
@app.get("/models/{model_id}")
def get_model(path: QueryModel):
"""查询模型详情"""
return {"model_id": path.model_id, "status": "running"}
if __name__ == "__main__":
app.run(debug=True)
我建议在 CI/CD 流程里加一道检查:如果当前分支改了 API 签名,但没更新 OpenAPI 文档,CI 直接报红。这能保证文档和代码始终同频。
文档里的好习惯
- 每个接口写清楚作用、业务场景
- 枚举字段给出可用值列表
- 错误返回给出示例和解决方案
- 标注接口的版本、作者、变更历史
看似麻烦,实际能省掉大量的群聊和会议。
知识体系总览
下面这张图把 RESTful API 设计的关键环节串起来了。从资源设计到文档生成,每个环节都有对应的原则和工具。你看一眼就能知道设计接口时该考虑哪些点。
以上就是 RESTful API 设计的五个关键点。说白了,就是把你脑子里的业务逻辑翻译成标准接口语言,让前后端、微服务之间能顺畅沟通。下一个环节,我们会把这些原则应用到实际的多 GPU 推理服务中,设计出一套可扩展、可维护的 API 体系。