Engineering Architecture
API 设计的艺术:从混乱到优雅的进化之路
RESTful 还是 GraphQL?版本控制怎么做?错误响应如何设计?分享我设计 50+ API 的实战经验,以及那些踩过的坑和学到的教训。
Ioodu · ·
Updated: Feb 10, 2026 · 22 min read
#API Design
#REST
#GraphQL
#Backend
#Architecture
好的 API 是什么样的
五年前,我设计的第一个 API 是这样的:
GET /getUserData?id=123
POST /saveNewUser
DELETE /deleteUserById
当时我觉得挺好的——功能实现了,前端能调用,测试通过了。
直到半年后,另一个团队需要集成我们的 API,他们发来的问题清单让我汗颜:
- “这个接口返回的数据结构为什么不一致?”
- “错误码为什么是 200 但 body 里有 error?”
- “分页怎么实现?”
- “这个字段是必填的吗?”
那一刻我意识到:写代码实现功能只是 20%,设计好 API 才是那 80%。
这篇文章分享我过去五年设计 50+ API 的经验教训,以及一套可落地的设计原则。
RESTful API 设计原则
1. 资源的命名
错误示范:
GET /getUsers
POST /createUser
PUT /updateUser
DELETE /deleteUser
正确示范:
GET /users # 获取用户列表
GET /users/{id} # 获取单个用户
POST /users # 创建用户
PUT /users/{id} # 全量更新用户
PATCH /users/{id} # 部分更新用户
DELETE /users/{id} # 删除用户
原则:
- 使用名词(users, orders, products),不是动词
- 使用复数形式
- 使用层级结构表达关系:
/users/{id}/orders
2. HTTP 方法的正确使用
| 方法 | 幂等性 | 用途 | 示例 |
|---|---|---|---|
| GET | 是 | 获取资源 | GET /users/123 |
| POST | 否 | 创建资源 | POST /users |
| PUT | 是 | 全量更新 | PUT /users/123 |
| PATCH | 否 | 部分更新 | PATCH /users/123 |
| DELETE | 是 | 删除资源 | DELETE /users/123 |
常见误区:
- 用 POST 做所有操作(包括更新和删除)
- 用 GET 做删除操作(浏览器预加载会误删!)
- 用 PUT 做部分更新(应该用 PATCH)
3. 状态码的语义
错误示范:
HTTP/1.1 200 OK
{
"success": false,
"error": "User not found"
}
正确示范:
HTTP/1.1 404 Not Found
{
"error": {
"code": "USER_NOT_FOUND",
"message": "User with id 123 does not exist",
"details": {
"userId": "123"
}
}
}
常用状态码:
200 OK- 成功201 Created- 创建成功204 No Content- 成功但无返回体(如 DELETE)400 Bad Request- 请求参数错误401 Unauthorized- 未认证403 Forbidden- 无权限404 Not Found- 资源不存在409 Conflict- 资源冲突(如重复创建)422 Unprocessable Entity- 业务逻辑错误429 Too Many Requests- 限流500 Internal Server Error- 服务器错误
请求与响应设计
1. 请求参数
路径参数 vs 查询参数 vs 请求体:
# 路径参数:标识资源
GET /users/{userId}
# 查询参数:过滤、排序、分页
GET /users?role=admin&sort=-createdAt&page=2&limit=20
# 请求体:创建/更新资源
POST /users
{
"name": "John Doe",
"email": "john@example.com"
}
2. 响应格式
统一包装:
{
"data": {
"id": "123",
"name": "John Doe",
"email": "john@example.com"
},
"meta": {
"requestId": "req_abc123",
"timestamp": "2026-03-21T10:30:00Z"
}
}
列表响应:
{
"data": [
{ "id": "1", "name": "User 1" },
{ "id": "2", "name": "User 2" }
],
"pagination": {
"page": 1,
"limit": 20,
"total": 100,
"totalPages": 5,
"hasNext": true,
"hasPrev": false
}
}
3. 字段命名规范
使用 camelCase:
{
"userId": "123",
"createdAt": "2026-03-21T10:30:00Z",
"isActive": true
}
时间格式:使用 ISO 8601
{
"createdAt": "2026-03-21T10:30:00Z", // UTC
"localTime": "2026-03-21T18:30:00+08:00" // 带时区
}
错误处理设计
1. 错误响应结构
{
"error": {
"code": "VALIDATION_ERROR",
"message": "Request validation failed",
"details": [
{
"field": "email",
"code": "INVALID_FORMAT",
"message": "Email format is invalid"
},
{
"field": "age",
"code": "OUT_OF_RANGE",
"message": "Age must be between 18 and 100"
}
],
"requestId": "req_abc123",
"timestamp": "2026-03-21T10:30:00Z",
"documentation": "https://api.example.com/docs/errors/VALIDATION_ERROR"
}
}
2. 错误码设计
格式:{DOMAIN}_{ERROR_TYPE}
USER_NOT_FOUND
USER_ALREADY_EXISTS
ORDER_INSUFFICIENT_INVENTORY
AUTH_INVALID_TOKEN
AUTH_TOKEN_EXPIRED
RATE_LIMIT_EXCEEDED
文档化:每个错误码都要有对应的文档说明
3. 错误日志
后端日志:
logger.error({
requestId: 'req_abc123',
userId: 'user_456',
errorCode: 'VALIDATION_ERROR',
errorMessage: 'Email format is invalid',
stack: error.stack,
path: '/api/users',
method: 'POST',
body: { name: 'John', email: 'invalid-email' },
timestamp: '2026-03-21T10:30:00Z'
});
分页设计
1. Offset-based 分页
适用:数据量小、实时性要求不高
GET /users?page=2&limit=20
响应:
{
"data": [...],
"pagination": {
"page": 2,
"limit": 20,
"total": 1000,
"totalPages": 50
}
}
缺点:
- 深度分页性能差(
OFFSET 100000) - 数据实时变化时可能重复或遗漏
2. Cursor-based 分页
适用:数据量大、实时性要求高(如消息流)
GET /messages?cursor=eyJpZCI6MTIzNDV9&limit=20
响应:
{
"data": [...],
"pagination": {
"nextCursor": "eyJpZCI6MTIzNjV9",
"prevCursor": "eyJpZCI6MTIzMjV9",
"hasNext": true,
"hasPrev": true
}
}
优点:
- 性能稳定
- 数据变化不影响结果
版本控制策略
1. URL 路径版本
GET /v1/users
GET /v2/users
优点:清晰、易于缓存 缺点:URL 变更
2. Header 版本
GET /users
Accept: application/vnd.api+json;version=2
优点:URL 不变 缺点:不够直观、缓存复杂
3. 我的推荐
新项目:直接 URL 版本 /v1/
成熟项目:Header 版本,逐步迁移
版本策略:
- 主版本号(v1, v2):破坏性变更
- 次版本号(v1.1):新功能,向后兼容
- 补丁版本:不体现在 API 中
认证与授权
1. JWT 最佳实践
Token 结构:
// Access Token(短期,15分钟)
{
"sub": "user_123",
"iss": "api.example.com",
"iat": 1711012200,
"exp": 1711013100,
"scope": ["read:users", "write:orders"],
"jti": "token_unique_id"
}
// Refresh Token(长期,7天)
{
"sub": "user_123",
"type": "refresh",
"exp": 1711617000,
"jti": "refresh_token_id"
}
安全建议:
- Access Token 短期有效(15-60 分钟)
- Refresh Token 轮换(每次刷新都生成新的)
- 使用 HTTPS only
- Token 存储在 httpOnly cookie 或 secure storage
2. OAuth 2.0 + PKCE
适用场景:第三方应用接入
# 1. 授权请求
GET /oauth/authorize?
response_type=code&
client_id=my_app&
redirect_uri=https://myapp.com/callback&
scope=read write&
state=random_state&
code_challenge=base64url(sha256(code_verifier))
# 2. 换取 Token
POST /oauth/token
{
"grant_type": "authorization_code",
"code": "auth_code",
"redirect_uri": "https://myapp.com/callback",
"client_id": "my_app",
"code_verifier": "random_string"
}
API 文档
1. OpenAPI (Swagger) 规范
openapi: 3.0.0
info:
title: User API
version: 1.0.0
paths:
/users:
get:
summary: List users
parameters:
- name: page
in: query
schema:
type: integer
default: 1
- name: limit
in: query
schema:
type: integer
default: 20
maximum: 100
responses:
'200':
description: List of users
content:
application/json:
schema:
$ref: '#/components/schemas/UserList'
2. 文档工具推荐
| 工具 | 特点 | 适用场景 |
|---|---|---|
| Swagger UI | 可视化、可交互 | 开发调试 |
| Redoc | 美观、只读 | 对外文档 |
| Postman | 集合分享、Mock | 团队协作 |
| Stoplight | 设计优先 | API 设计阶段 |
性能优化
1. 响应压缩
Accept-Encoding: gzip, deflate, br
建议:> 1KB 的响应都开启压缩
2. 缓存策略
# 客户端缓存
Cache-Control: max-age=3600, private
# CDN 缓存
Cache-Control: max-age=86400, public
ETag: "abc123"
Last-Modified: Mon, 21 Mar 2026 10:00:00 GMT
缓存层级:
- 浏览器缓存(客户端)
- CDN 缓存(边缘节点)
- 应用缓存(Redis/Memcached)
- 数据库缓存(Query Cache)
3. 限流(Rate Limiting)
# 响应头
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 95
X-RateLimit-Reset: 1711015800
# 超限返回
429 Too Many Requests
Retry-After: 60
限流算法:
- 固定窗口:简单,但可能有 burst
- 滑动窗口:更平滑
- 令牌桶:允许一定 burst
GraphQL:REST 的替代方案
什么时候选 GraphQL
适合:
- 前端需求多变,经常需要不同字段组合
- 移动端,需要精简响应
- 聚合多个数据源
不适合:
- 简单 CRUD
- 文件上传/下载
- 团队缺乏 GraphQL 经验
GraphQL 设计原则
type User {
id: ID!
name: String!
email: String!
orders: [Order!]!
createdAt: DateTime!
}
type Query {
user(id: ID!): User
users(
filter: UserFilter
sort: SortInput
pagination: PaginationInput
): UserConnection!
}
type Mutation {
createUser(input: CreateUserInput!): User!
updateUser(id: ID!, input: UpdateUserInput!): User!
deleteUser(id: ID!): Boolean!
}
N+1 问题解决
DataLoader:
const userLoader = new DataLoader(async (userIds) => {
// 批量查询:SELECT * FROM users WHERE id IN (...)
const users = await db.users.findMany({
where: { id: { in: userIds } }
});
return userIds.map(id => users.find(u => u.id === id));
});
// 使用
const user = await userLoader.load(userId);
测试策略
1. 契约测试(Contract Testing)
Pact 示例:
// Consumer 测试
const pact = new Pact({
consumer: 'Frontend',
provider: 'UserAPI'
});
await pact
.interaction()
.given('user exists')
.uponReceiving('get user by id')
.withRequest({
method: 'GET',
path: '/users/123'
})
.willRespondWith({
status: 200,
body: {
id: '123',
name: 'John Doe'
}
});
2. API 自动化测试
describe('Users API', () => {
it('should create a user', async () => {
const response = await request(app)
.post('/api/v1/users')
.send({
name: 'John Doe',
email: 'john@example.com'
})
.expect(201);
expect(response.body.data).toMatchObject({
name: 'John Doe',
email: 'john@example.com'
});
expect(response.body.data.id).toBeDefined();
});
it('should return 400 for invalid email', async () => {
await request(app)
.post('/api/v1/users')
.send({
name: 'John',
email: 'invalid-email'
})
.expect(400);
});
});
监控与可观测性
1. 关键指标
黄金三指标:
- Latency:P50, P95, P99 响应时间
- Error Rate:4xx, 5xx 错误率
- Throughput:QPS/TPS
业务指标:
- 接口调用量
- 用户活跃度
- 错误分布
2. 分布式追踪
// OpenTelemetry 示例
const span = tracer.startSpan('process_order');
try {
span.setAttribute('order.id', orderId);
span.setAttribute('user.id', userId);
await processOrder(orderId);
span.setStatus({ code: SpanStatusCode.OK });
} catch (error) {
span.recordException(error);
span.setStatus({
code: SpanStatusCode.ERROR,
message: error.message
});
throw error;
} finally {
span.end();
}
总结:API 设计 checklist
设计阶段
- 资源命名符合 RESTful 规范
- HTTP 方法和状态码使用正确
- 错误响应结构统一
- 分页策略合适
- 认证授权机制明确
- 版本策略确定
开发阶段
- OpenAPI 文档完整
- 输入验证完备
- 错误处理全面
- 单元测试覆盖
- 集成测试通过
- 性能测试达标
部署阶段
- 监控告警配置
- 日志收集配置
- 限流熔断配置
- 缓存策略生效
- 文档站点部署
运维阶段
- 错误率监控
- 性能指标监控
- 用户反馈收集
- 版本兼容性检查
- 定期安全审计
推荐资源
书籍:
- 《RESTful Web APIs》Leonard Richardson
- 《API Design Patterns》JJ Geewax
- 《GraphQL in Action》Samer Buna
工具:
- OpenAPI Generator - 代码生成
- Insomnia - API 调试
- Hoppscotch - 开源 Postman 替代品
规范:
- JSON:API - 标准化 JSON API
- Microsoft REST API Guidelines
- Google API Design Guide
你的 API 设计遇到过什么坑?欢迎在评论区分享。
本文总结了 5 年的 API 设计血泪史,希望能帮你少走一些弯路。