稳定 API 错误模型
把网络错误、HTTP status、应用 code、用户文案、内部 cause 与 requestId 分层,形成可版本化、可观测且不泄露内部实现的 API 错误契约。
[!info] related notes
- 所属:接口规范 · Node.js 后端面试 MOC
- 客户端:前端 HTTP 状态码处理
- 观测:多服务分布式追踪
稳定 API 错误模型
核心问题
客户端需要根据错误采取可预测动作,但内部异常文本会随着数据库、框架、Provider 和实现重构而变化,还可能泄露 SQL、路径、堆栈或敏感信息。
因此要把错误至少拆成:
Transport / Network Error
HTTP Status
Application Code
User-facing Message
Structured Details
Request / Trace Identifier
Internal Cause / Stack
推荐响应
{
"error": {
"code": "VERSION_CONFLICT",
"message": "数据已被其他操作更新,请刷新后重试。",
"details": {
"expectedRevision": 12,
"actualRevision": 13
},
"requestId": "req_..."
}
}
status:协议级类别,例如 401、403、409、422、429、500;code:稳定、可版本化的应用语义;message:面向用户,可本地化,不作为程序分支;details:只有客户端确实需要的结构化信息;requestId:安全的报障关联标识;cause/stack/provider payload:只进入受控日志和 trace。
分层规则
网络错误
DNS、connect、TLS、timeout 或连接中断可能没有 HTTP response。客户端要单独建模,不能伪造成 500。
HTTP Status
Status 表达广义语义,不承担全部业务细节:
401:缺少或失效的认证;403:身份已知但无权限;409:当前资源状态、revision 或唯一约束冲突;422:可解析但业务输入/字段校验失败;429:限流;5xx:服务端未能完成请求。
团队应保持一致,不把状态码选择变成没有业务价值的教条。
Application Code
Code 应描述客户端需要区分的稳定语义:
AUTH_EXPIRED;PERMISSION_DENIED;VERSION_CONFLICT;RUN_CANCELLED;RUN_WAITING_FOR_APPROVAL;RATE_LIMITED。
不要把每个 ORM exception 或 Provider error 暴露成公共 code。
Domain Error 与 Infrastructure Error
领域错误是预期分支,例如任务状态不允许完成;基础设施错误是 DB、网络、Provider 等实现故障。两者都可以映射到稳定 API error,但日志和告警级别不同。
Domain Error
→ 稳定 code + 可操作 message
→ 通常不需要 error alert
Infrastructure Error
→ generic public code
→ internal cause + trace + alert
Streaming / Agent 场景
HTTP headers 发出后不能再改变 status。流式协议必须有结构化 error event:
{
"type": "run_failed",
"runId": "run_...",
"code": "MODEL_TIMEOUT",
"message": "本次生成未完成。",
"requestId": "req_..."
}
连接中断不等于 run_failed,更不等于完成。客户端应通过 durable run state 或重连协议确认最终状态。
重试信息
服务可以提供机器可读重试提示,但不能诱导危险自动重试:
{
"code": "RATE_LIMITED",
"retryable": true,
"retryAfterMs": 1200
}
是否真正重试还取决于:
- 操作是否幂等;
- body 是否可 replay;
- 是否已经产生副作用;
- 当前 retry budget;
- 是否会形成重试风暴。
安全边界
- 不回传 stack、SQL、文件路径、Provider secret 和内部 host;
404可以在防资源枚举场景隐藏“存在但无权限”;- details 必须经过字段 allowlist 和权限检查;
- 日志中的用户数据和 token 同样需要脱敏;
- requestId 不能编码租户秘密,也不能作为授权凭据。
Contract Test
至少验证:
- 同一领域错误在 HTTP 和 IPC adapter 中映射为同一
code; - 401 不会被误映射为 403;
- 未知异常不会泄露内部 message;
- 字段错误具有稳定 details schema;
- Streaming terminal error 与 durable run state 一致;
- 旧客户端遇到新 code 有 generic fallback。
面试回答
HTTP status 只表达协议层类别,客户端真正稳定分支应依赖版本化 application code;message 用于展示,不用于判断。响应还可以带结构化 details 和 requestId,内部 stack/cause 只进入日志。网络错误、领域错误和基础设施错误要分层;在 SSE 等流式场景,headers 发出后还需要稳定的 error event 和独立的 durable run terminal state。