Skip to content

API 响应模型的设计哲学与范式

梳理成功与失败响应的设计差异背后的逻辑,以及主流 API 的实践与取舍

发布时间2026-03-08 15:30:00
引用

成功只有一个结果,失败却有多种原因

以前我对 API 响应的理解是:无论成功还是失败,都应该用统一的模型包装,例如 { code, message, data },通过 code 判断成功与否,data 承载结果。以前公司的设计也是如此——所有接口都返回带包装的格式,成功和失败共用一套信封。

经过一轮思考和对比主流实践后,我的结论是:成功和失败的信息结构应当不同。成功时,依靠 HTTP 状态码即可表达「请求成功」,Body 直接返回资源;失败时,则需要用结构化的 codemessage 同时告诉程序(为何失败、如何分支)和(如何展示、如何解释)。信封式的统一包装并非必须,甚至会带来冗余。

下文展开这种设计背后的逻辑与取舍。

一、两种设计路线

API 响应模型本质要回答三个问题:客户端如何知道请求成功还是失败?成功时数据在哪?失败时如何得知原因?

由此衍生出两条路线:

路线思路成功失败
用 HTTP 表达语义状态码负责成功/失败,Body 负责业务内容200 + 直接返回资源4xx/5xx + 错误描述
用 Body 表达语义不依赖状态码{ success: true, data }{ success: false, error }

主流实践(Stripe、GitHub、微信、支付宝)多采用前者:让 HTTP 状态码承担「成功/失败」的语义,Body 专注承载业务数据或错误详情

二、成功响应:为什么直接返回资源?

设计理念:成功时,客户端要的就是资源本身。

  • 资源已包含所需信息,无需再包一层 { data: resource }
  • 解析更直接:const user = response 而不是 const user = response.data
  • 减少冗余:200 已表示成功,success: true 显得多余

因此常见做法是:

GET /users/123 → 200
{ "id": "123", "display_name": "张三", ... }

而不是:

json
{ "success": true, "data": { "id": "123", ... } }

三、失败响应:为什么需要 code 和 message?

设计理念:失败时,信息是不对称的——原因多样,需要结构化表达。

HTTP 状态码能表达的

状态码含义
400请求有问题
401未认证
403无权限
404资源不存在
422校验失败
429请求过多
500服务端错误

状态码只告诉你错误大类,不告诉你具体是哪一种。同为 400,可能是手机号格式错误、验证码错误、验证码过期——客户端无法区分。

code 和 message 各司其职

字段受众作用
code程序机器可读,用于分支逻辑、重试策略、监控统计
message用户人类可读,用于 Toast/Alert 直接展示

示例:

swift
if error.code == "INVALID_CODE" {
    showAlert("验证码错误,请重新输入")
} else if error.code == "CODE_EXPIRED" {
    showAlert("验证码已过期,请重新获取")
}

小结:成功与失败的不对称性

  • 成功:结果单一,靠 HTTP 状态码(200)表达成功,Body 直接返回资源即可。
  • 失败:原因多样,需要同时告诉人和机器——code 给程序做分支与监控,message 给人展示,必要时用 details 给出校验明细。

这种不对称性,正是成功和失败设计哲学不同的根源。

四、主流实践一览

服务成功错误
Stripe直接返回对象{ "error": { "code": "...", "message": "..." } }
GitHub直接返回对象/数组{ "message": "...", "errors": [...] }
微信开放平台直接返回业务字段{ "errcode": 40001, "errmsg": "..." }
支付宝直接返回业务字段{ "code": "10000", "msg": "...", "sub_code": "..." }

共同点:成功时直接返回资源,失败时用简单 JSON 表达 code/message,不套多层 envelope

五、推荐的错误结构

json
{
  "error": {
    "code": "INVALID_CODE",
    "message": "验证码错误或已过期",
    "details": [
      { "field": "code", "message": "必须是 6 位数字" }
    ]
  }
}
  • code:业务错误码,用于程序判断。
  • message:用户可见说明。
  • details:可选,校验失败时逐字段说明。

六、设计原则小结

  1. HTTP 能表达的交给 HTTP:状态码表示 success/fail,Body 表示业务内容。
  2. 成功时 Body 就是你要的:不必再包一层 data
  3. 失败时需要结构化code + message,必要时加 details
  4. 简单优于复杂:够用即可,不追求 RFC 7807 等完整规范。
  5. 保持一致性:成功和失败都用统一格式,减少客户端分支逻辑。
Powered by QianFan | Copyright © 2023.3.6-2026 | MIT License