接口契约描述客户端可以依赖什么:请求字段、身份、成功结果、错误、重试方式以及版本变化。只列一个 URL 和 JSON 示例,无法回答“响应丢了以后能不能重发”。先读网络与 HTTP 基础,再把协议语义落实到服务行为。

先定义一次预约

假设接口为 POST /reservations,请求数量必须为正整数,预约属于当前登录用户。接口返回 201 Created、Location: /reservations/42 和预约记录。客户端提供 Idempotency-Key,同一用户用相同键与相同请求重试,取回原记录;同键换参数返回 409。这是业务约定,不能认为 HTTP 会自动实现。

POST /reservations HTTP/1.1
Content-Type: application/json
Idempotency-Key: reserve-20261024-001

{"sku":"book-1","quantity":2}
情况响应客户端下一步
JSON 语法错或字段不合法400改请求,原样重试没有用
没有通过身份验证401完成身份验证
身份已知但没有权限403不能通过换参数绕过
库存不够或幂等键冲突409根据业务状态处理
被限流429尊重 Retry-After,并限制重试次数
服务内部错误500记录请求标识;是否重试取决于操作语义

错误响应可以统一为 {"code":"OUT_OF_STOCK","message":"库存不足","requestId":"..."}。客户端依据稳定的 code 决策,message 给人阅读;不要把 SQL、堆栈或密钥返回给客户端。生产接口也可采用 RFC 9457 的 Problem Details,关键是保持契约一致。

幂等不等于每次响应完全相同

GET 应是安全方法,不用它执行扣款或删除。PUT 和 DELETE 的幂等性描述重复执行的预期服务器效果;第一次删除返回 204、第二次返回 404,仍可能符合幂等语义。POST 默认没有这一保证,预约和支付需通过幂等键、唯一约束及事务建立业务幂等。

超时只说明客户端没按时拿到结果。服务器可能已提交数据,盲目换键重试会创建第二条记录。事务与隔离负责原子提交,消息投递讨论跨进程后的重复执行。

分页、更新与通信方式

对不断新增的记录,用稳定排序 (created_at DESC, id DESC) 配合游标。游标包含最后一条记录的两个排序值,服务端继续查询更小的元组,避免仅按时间排序漏掉同一时刻的记录。游标应校验格式并限制页大小,不能把客户端传来的 SQL 片段拼进去。

更新会被多人同时修改的资料时,可使用版本号或 ETag 与 If-Match;版本不符返回 412 Precondition Failed,让客户端重新读取。新增可选字段通常比改名、改类型安全;删除字段要考虑旧客户端的实际存活周期。

需求可选方式需要承担的成本
公开资源接口、容易调试HTTP + JSON契约、版本与错误需要明确约定
内部强类型调用、流式 RPCgRPCIDL、代码生成、代理与客户端兼容
服务器持续单向推送SSE重连、事件标识、代理缓冲和连接数
双向实时交互WebSocket消息协议、心跳、背压、重连和鉴权

动手检查

运行 Go HTTP 服务实验,分别发送合法 JSON、未知字段、两个连续 JSON 和超过限制的请求体。解释为什么“能解码第一个对象”还不足以证明整个请求合法。

再为预约接口列出四个用例:首次成功、同键重试、同键不同参数、两个并发请求。每个用例写清响应与数据库最终状态。不要只检查状态码。

面试自查

  • GET、PUT、POST 的安全性与幂等性分别指什么?为什么网络超时不能直接判定操作失败?
  • 游标分页为什么需要唯一的排序末项?它能否天然保证跨页读取的是同一个数据快照?
  • 用状态码表达所有业务错误,与所有响应都返回 200,各会损失什么信息?

参考资料