3. HTTP协议与企业级API设计原则
企业级后端系统的对外能力,最终几乎都收敛为一组 HTTP API。理解 HTTP 的请求响应模型,不是为了背诵报文格式,而是为了在设计接口时知道“哪些语义已经由协议规定、哪些必须由业务契约补齐”。一次请求由方法、URL、请求头、可选请求体组成;一次响应由状态码、响应头、可选响应体组成。方法表达动作意图,URL 定位资源,头部承载元数据与协商信息,状态码表达处理结果类别,响应体承载资源表示。RESTful 风格的核心,就是让这五者各司其职,而不是把所有信息都塞进 URL 或统一返回 200。
先看状态码的工程含义。2xx 表示成功:200 用于查询或更新后返回表示,201 用于创建成功并通常配合 Location 头,204 用于删除或更新成功且无需返回体。4xx 表示客户端问题:400 参数格式错误,401 未认证,403 已认证但无权限,404 资源不存在,409 冲突(如重复创建、版本冲突),422 语义校验失败,429 触发限流。5xx 表示服务端问题:500 未预期异常,503 依赖不可用。企业级 API 应避免“HTTP 200 + 业务错误码”的万能返回,因为网关、监控、重试策略、客户端 SDK 都依赖状态码做第一层判断。
头部与内容协商决定“同一资源如何以不同表示交付”。Content-Type 声明请求体或响应体的媒体类型,Accept 表达客户端期望的表示,Content-Language、Accept-Language 处理多语言,ETag 与 If-None-Match 支持缓存与并发控制,Idempotency-Key 用于支付、下单等需要幂等保障的写操作。内容协商不是可选项:当客户端发送 Accept: application/json 时,服务端不应返回 HTML 错误页;当请求体是 application/json 时,服务端应拒绝解析表单编码。
RESTful 资源命名应遵循“名词复数、层级表达从属、避免动词”的原则。例如订单资源用 /api/v1/orders,单个订单用 /api/v1/orders/{order_id},订单项用 /api/v1/orders/{order_id}/items。动作若无法用标准方法表达,可用子资源或受控动作端点,如 POST /api/v1/orders/{order_id}/cancel,但应克制使用。版本控制建议放在 URL 路径中(/api/v1/),因为它在网关路由、日志、缓存和客户端升级中最直观;也可用 Accept 头做媒体类型版本,但调试成本更高。企业项目通常选择路径版本 + 兼容性策略:新增字段不破坏旧客户端,删除字段需走弃用周期。
幂等性是订单类 API 的生命线。GET、PUT、DELETE 按 HTTP 语义应幂等,POST 默认不幂等。下单接口若因网络超时被客户端重试,可能产生两笔订单。解决方案是客户端生成唯一 Idempotency-Key,服务端在 Redis 或数据库中记录该键与首次处理结果,重复请求直接返回首次结果。PUT 更新订单地址时,天然幂等:同一请求执行多次结果一致。DELETE 删除订单也应幂等:首次返回 204,再次返回 404 或 204 均可,但契约必须明确。
下面用一张契约关系图,把订单管理 API 的资源、方法、状态码、头部与幂等策略放在同一张图上,便于对照设计。
把上述契约落到订单管理 API 上,可以形成一份可直接评审的接口清单。以下示例参数均为教学假设,用于说明字段与行为,不代表真实业务数据。
| 方法与路径 | 语义 | 成功状态码 | 关键头部 | 幂等性 |
|---|---|---|---|---|
| GET /api/v1/orders | 分页查询订单 | 200 | Accept、ETag | 天然幂等 |
| POST /api/v1/orders | 创建订单 | 201 + Location | Idempotency-Key、Content-Type | 依赖幂等键 |
| GET /api/v1/orders/{id} | 查询单个订单 | 200 | ETag、If-None-Match | 天然幂等 |
| PUT /api/v1/orders/{id} | 全量更新订单 | 200 或 204 | If-Match、Content-Type | 天然幂等 |
| DELETE /api/v1/orders/{id} | 删除订单 | 204 | — | 天然幂等 |
| POST /api/v1/orders/{id}/cancel | 取消订单 | 200 | Idempotency-Key | 建议幂等 |
创建订单的请求体可设计为:{"customer_id":"C1001","items":[{"sku":"SKU-9","qty":2,"price":19.90}],"currency":"CNY"}。服务端校验通过后返回 201,响应头带 Location: /api/v1/orders/ORD-20240501-001,响应体返回订单号、状态 CREATED、总金额与创建时间。若同一 Idempotency-Key 再次到达,服务端不再创建新订单,而是返回首次的 201 结果或 200 结果,并在响应头标注 Idempotent-Replayed: true,便于客户端识别。
版本控制方面,/api/v1/ 一旦发布,字段只增不减;若必须删除字段,先标记 deprecated,在响应头加 Deprecation 与 Sunset,给客户端至少一个迭代周期的迁移时间。内容协商失败时,服务端应返回 406 而非 500;请求体媒体类型不支持时返回 415。这些细节决定了 API 在网关、监控、SDK 和多方联调中的可预测性。
最后强调,RESTful 不是教条。订单取消、支付回调、批量导入等场景,允许使用受控动作端点或异步任务端点,但必须在契约中明确方法、状态码、幂等键和错误结构。企业级 API 的价值不在于“像 REST”,而在于让调用方在超时、重试、并发、版本升级时都有确定行为可依赖。