API Design(API 设计)
解决什么问题:让跨服务 / 跨团队的接口"好调用、好演化、好测试"—— 设计差的 API = 调用方吐槽、改一字段全链路崩、版本混乱。 设计好的 API = 接口稳定、语义清晰、错误明确、演化有路径。
它是什么
API Design 是 L2 设计层关于外部接口契约的工程方法——包括:
- RESTful API 设计(HTTP + JSON,主流)
- RPC / gRPC API(强类型 + 高性能)
- GraphQL API(前端驱动的灵活查询)
- API 风格选择:REST vs RPC vs GraphQL vs 混合
核心问题:
- 资源如何抽象?(URL 设计 / protobuf message)
- 状态如何表达?(HTTP status / gRPC status)
- 错误如何传达?(错误码 + 错误消息)
- 版本如何演化?(URL 版本 / Header 版本 / 语义版本)
- 演化如何兼容?(新增字段 OK,删除字段 / 改类型危险)
SE@G Ch 18:"The most important principle of API design is consistency. Once you've chosen a style, apply it everywhere."
V2 视角(V2.7 B4 段 4 抽象 + B3 段 5 团队可拥有):API 是 BC 之间的"宪法"——一旦发布,每个调用方都是你的客户,破坏兼容 = 法律纠纷。
关键属性
| 属性 | 值/含义 |
|---|---|
| 契约 | OpenAPI / protobuf IDL / GraphQL SDL |
| 一致性 | 命名 / 错误 / 分页 / 过滤全 API 一致 |
| 版本 | URL 路径 / Header / 媒体类型 |
| 兼容 | 只能加不能减(减 = 主版本升级) |
| 错误 | 错误码 + 消息 + 详情(结构化) |
| 文档 | 自动生成 + 人类可读 + 范例 |
| 反模式 | 一次性 API、用着用着改字段、错误码不统一 |
| 判错标志 | 文档和实现不一致 / 调用方要看代码 |
怎么用
场景 1:RESTful API 设计
资源抽象:
GET /orders ← 列表
POST /orders ← 创建
GET /orders/{id} ← 单个
PATCH /orders/{id} ← 部分更新
DELETE /orders/{id} ← 删除
GET /orders/{id}/items ← 子资源
POST /orders/{id}/items ← 添加子资源命名原则:
- 资源用复数名词(
/orders不/order) - 不用动词(
/getOrder→ ❌,GET /orders/{id}→ ✅) - 用连字符不用下划线(
/order-items✅)
场景 2:OpenAPI 契约优先(API-First)
# openapi.yaml
openapi: 3.1.0
info:
title: Order API
version: 2.1.0
paths:
/orders:
post:
operationId: placeOrder
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/PlaceOrderRequest'
responses:
'201':
description: Order placed
content:
application/json:
schema:
$ref: '#/components/schemas/OrderResponse'
'400': { $ref: '#/components/responses/ValidationError' }
'409': { $ref: '#/components/responses/ConflictError' }
'422': { $ref: '#/components/responses/BusinessError' }
components:
schemas:
PlaceOrderRequest:
type: object
required: [customerId, items]
properties:
customerId: { type: string, format: uuid }
items:
type: array
minItems: 1
items: { $ref: '#/components/schemas/OrderItemInput' }契约优先的好处:
- 代码生成(Server Stub + Client SDK)
- 文档自动生成
- 前后端并行开发(基于契约)
- 测试自动生成
场景 3:版本演化策略
V2 B7 段 4 演化视角:API 演化有 4 种策略:
1. URL 路径版本 /v1/orders /v2/orders ← 最简单,推荐
2. Header 版本 Accept: application/vnd.myapi.v2+json
3. 媒体类型版本 Content-Type: application/vnd.myapi.v2+json
4. 无版本(持续演化) ← 只有内部 API 可用演化兼容性规则:
- ✅ 可以:加字段、加端点、加可选参数
- ⚠️ 慎重:删字段(客户端可能还在读)、改字段类型
- ❌ 必须新版本:删端点、改 URL 语义、改必填
示例——给 Order 加 notes 字段:
{
"id": "ord-001",
"customerId": "cus-123",
"items": [...],
+ "notes": "VIP 客户优先发货" ← 新字段,兼容
}老客户端忽略 notes → 无破坏。新客户端读 notes → 受益。
场景 4:结构化错误响应
{
"error": {
"code": "ORDER_INSUFFICIENT_STOCK",
"message": "商品 SKU-001 库存不足",
"details": {
"productId": "SKU-001",
"requested": 10,
"available": 3
},
"traceId": "abc-123-xyz",
"documentation": "https://docs.example.com/errors/ORDER_INSUFFICIENT_STOCK"
}
}HTTP status 语义:
- 400:参数错(语法 / 验证)
- 401 / 403:认证 / 授权
- 404:资源不存在
- 409:状态冲突(已支付 / 已发货)
- 422:业务规则违反(库存不足)
- 429:限流
- 5xx:服务端故障
V2 B5 段 3 "反馈":错误响应是反向反馈——告诉调用方"什么错了、为什么、怎么办"。
场景 5:gRPC vs REST 选型
场景 → 推荐
浏览器 / 移动端 / 公开 API → REST + JSON
服务间高频调用 / 强类型需求 → gRPC
流式(双向 / 长连接) → gRPC streaming
前端驱动灵活查询 → GraphQL
异构系统集成(第三方) → REST + JSONgRPC 的优势:强类型 IDL(protobuf)、二进制高性能、双向流、内置超时 / 重试 / 熔断。 gRPC 的劣势:浏览器不能直接调(需要 grpc-web 网关)、调试不直观。
常见误区
- "API 设计 = URL 设计"——错。URL 只是表层,契约 + 错误 + 版本 + 演化才是核心
- "暴露数据库表结构"——错。API 是业务抽象,不是数据 schema。Order 是 API,不是 table_orders
- "加字段 = 改字段"——错。只能加字段,改类型必须新版本
- "错误码 = HTTP status"——错。HTTP status 是协议层,业务错误应该用业务错误码(如
ORDER_INSUFFICIENT_STOCK),HTTP status 配 422 - "API 文档一次性写完"——错。API 文档 = 契约,代码改 = 文档改。用 OpenAPI 自动生成
- "REST = 不用 RPC"——半对。REST 适合外部,gRPC 适合内部。混合用是常态
关键洞察
API 是系统间的"宪法",设计原则比 URL 美观重要 100 倍。
一致性、兼容性、可演化性——三个特性决定了 API 的"寿命"。
V2 B4 抽象 + B7 演化 的组合拳: 把 API 当成"抽象端口"——业务核心不依赖 API 具体格式, API 当作业务边界的"宪法",永远可演化、永远兼容。
相关链接
- Interface Contract——API 内部的接口契约
- Event Contract——API 的异步版(事件)
- Integration Maturity Model——API 在集成中的角色
- [V3 涌现作为] · 抽象——API 是抽象端口
- [V3 涌现之时] · 演化——API 必须可演化
- [V2 B6 折衷] · 折衷——REST vs gRPC 的选型