API 服务
设计、构建并部署生产级 API,配备认证、限流和监控。
API 设计
定义端点、数据契约、认证模型和 API 版本策略。
为什么
API 设计是比实现更长命的契约。外部开发者一旦依赖你的端点,破坏性变更让他们数天返工,让你失去信任。一开始就设计好,意味着数年无痛演进。
何时使用
在写任何端点代码之前。范围明确的 API 通常 1-2 周。2-3 名工程师的设计评审能发现 80% 的设计缺陷。
如何做
1. 定义资源和关系
- 列出领域所有名词(User、Order、Product)
- 映射关系:一对多、多对多
- 根据客户端需求选 REST 或 GraphQL
2. 设计 URL 结构(REST)
- 基于资源:/api/v1/users、/api/v1/orders
- 子资源嵌套:/api/v1/users/:id/orders
- 复数名词、kebab-case、最多 2 层嵌套
3. 定义数据契约
- 用 OpenAPI/Swagger 文档化每个端点
- 指定请求体、参数、查询、响应、错误
- 统一命名:snake_case 或 camelCase(选一个,坚持)
- 第一天就版本化:/api/v1/...
4. 设计认证
- 服务间用 API Key(简单、可撤销)
- 第三方访问用 OAuth 2.0(复杂但标准)
- 无状态认证用 JWT(短过期 + 刷新令牌)
- 永远不在 URL 参数中传认证(用 header)
5. 规划演进
- 仅加法变更(新字段、新端点)— 永不删除
- 弃用:标记旧字段、返回警告 header、6 个月后移除
- 版本化:URL 路径(/v1/、/v2/)用于破坏性变更
示例
OpenAPI 规格草图:
GET /api/v1/users — 列表(分页)
POST /api/v1/users — 创建
GET /api/v1/users/:id — 获取单个
PATCH /api/v1/users/:id — 部分更新
DELETE /api/v1/users/:id — 删除(软删除)
GET /api/v1/users/:id/orders — 列出用户订单
认证:Authorization header 中的 Bearer 令牌
Authorization: Bearer <api_key>
响应格式:
{ "data": {...}, "meta": { "page": 1, "total": 42 } }
错误格式:
{ "error": { "code": "NOT_FOUND", "message": "..." } }
常见问题
- ⚠命名不一致:某些端点 camelCase,其他 snake_case
- ⚠无版本化:首次破坏性变更疏远所有现有客户端
- ⚠URL 中用动词:/api/getUser 而非 /api/users/:id — 违反 REST 约定
- ⚠泄露内部 ID:顺序 ID 暴露用户数并使枚举攻击成为可能
最佳实践
- ✓先写 OpenAPI 规格再编码 — 它既是契约也是文档
- ✓所有公开 ID 用 UUID — 不用顺序整数
- ✓返回一致的错误结构:{ error: { code, message, details? } }
- ✓URL 中版本化(/v1/)而非 header — 可见且可缓存
实现
构建端点,配备验证、错误处理、限流和测试。
为什么
API 实现质量直接决定可用性、安全性和开发者体验。一个未验证的输入可导致数据泄露。一个缺失的限流可导致 DDoS。这些不是理论风险 — 它们是 API 事件最常见的原因。
何时使用
设计审批后。20-30 端点的 API 通常 4-8 周。先建认证和限流 — 它们是横切的且难以改造。
如何做
1. 搭建框架和中间件
- 选框架:Fastify(Node)、FastAPI(Python)、Gin(Go)
- 加中间件:CORS、请求日志、错误处理
- 设置 OpenAPI 验证中间件(如 zod-to-openapi)
2. 先实现认证和限流
- 认证中间件:验证 Bearer 令牌,将 user 挂到 request
- 限流:每 API Key 100 请求/分钟(分布式用 Redis)
- 超限时返回 429 + Retry-After header
3. 按依赖顺序构建端点
- 每个资源的 CRUD:list、get、create、update、delete
- 用 zod/joi 验证输入 — 早拒绝,快失败
- 多步写入用事务
- 返回一致的响应结构
4. 正确处理错误
- 400 验证错误(含字段详情)
- 401 缺失/无效认证
- 403 权限不足
- 404 不存在(不泄露存在性)
- 500 意外错误(记完整堆栈,返回通用信息)
5. 每个端点写测试
- 正常路径 + 验证错误 + 认证失败 + 边界情况
- 集成测试用真实数据库(测试事务回滚)
- 关键端点用 k6 或 Artillery 压测
示例
Fastify 端点 + 验证:
server.post('/api/v1/users', {
schema: {
body: zodToJsonSchema(userCreateSchema),
},
preHandler: [authMiddleware, rateLimit],
}, async (request, reply) => {
const user = await db.user.create({
data: request.body,
});
return reply.code(201).send({ data: user });
});
限流中间件(Redis 支撑):
const limited = await redis.incr(`rl:${apiKey}`);
if (limited > 100) {
return reply.code(429).header('Retry-After', '60').send({
error: { code: 'RATE_LIMITED', message: 'Too many requests' }
});
}
常见问题
- ⚠无输入验证 — 信任客户端数据导致 SQL 注入和数据损坏
- ⚠错误响应不一致 — 客户端无法编程处理错误
- ⚠无限流 — 一个客户端可耗尽所有人的数据库连接
- ⚠N+1 查询:列表端点逐项获取关联数据而非批量
最佳实践
- ✓用 schema 库(zod/joi)验证每个输入 — 永不信任客户端数据
- ✓用一致的错误信封:{ error: { code, message, details? } }
- ✓第一天就加限流 — 这是安全特性,不是功能请求
- ✓用 DataLoader 或批量查询消除 N+1 模式
部署与监控
零停机部署到生产,配备监控、告警和运维手册。
为什么
API 部署是代码与现实的碰撞。没有监控就是盲飞 — 麻烦的第一个信号是客户投诉。没有零停机部署,每次发布都可能丢弃进行中的请求。没有运维手册,凌晨 3 点事件变成数小时中断。
何时使用
实现测试完毕并已 staging 时。首次生产部署前就建监控 — 你需要基线指标来检测异常。
如何做
1. 选择部署平台
- 容器:Docker + Fly.io / Railway / AWS ECS
- Serverless:Vercel / Cloudflare Workers(边缘 API)
- VM:VPS 上 Docker Compose(最简单,最不可扩展)
2. 建 CI/CD 流水线
- 打 tag 时构建 Docker 镜像
- 推到镜像仓库
- 部署到 staging,跑冒烟测试
- 手动提升到生产
3. 配置零停机部署
- 健康检查端点:GET /health 返回 200
- 滚动更新:新实例启动,旧实例排空
- 数据库迁移:在应用启动前运行,向后兼容
4. 建监控和告警
- 可用性:每 30 秒 ping /health,连续 3 次失败告警
- 延迟:每端点 p50、p95、p99(Datadog/Grafana)
- 错误率:5xx 超 1% 告警
- 饱和度:CPU、内存、DB 连接、队列深度
5. 为常见事件写运维手册
- 数据库宕机:故障转移步骤,联系谁
- 高错误率:如何回滚,如何读日志
- 限流超限:如何识别客户端,如何调整
- 手册放代码仓库中,紧邻代码
示例
Dockerfile(多阶段):
FROM node:20 AS build
COPY . . && RUN npm ci && npm run build
FROM node:20-slim
COPY --from=build /app/dist ./dist
CMD ["node", "dist/server.js"]
健康检查端点:
GET /health -> 200 { "status": "ok", "db": "connected" }
部署流水线:
git tag v1.2.0 -> 构建镜像 -> 推到仓库
-> 部署 staging -> 跑冒烟测试
-> 手动审批 -> 滚动部署生产
-> 监控错误率 10 分钟
告警(PagerDuty):
- 5 分钟窗口内可用性 < 99.5%
- p95 延迟 > 500ms 持续 10 分钟
- 5xx 错误率 > 1% 持续 5 分钟
- DB 连接池 > 80% 饱和
常见问题
- ⚠无健康检查 — 负载均衡器不知道应用是否就绪
- ⚠部署时未跑迁移 — 新代码期望新 schema,启动崩溃
- ⚠告警疲劳:太多低优先级告警 -> 工程师忽视 PagerDuty
- ⚠无回滚计划:下午 3 点错误部署,团队手忙脚乱回滚 2 小时
最佳实践
- ✓第一天就建健康检查 — /health 返回 DB + 缓存 + 队列状态
- ✓应用启动前跑迁移,确保向后兼容
- ✓告警可操作:每条告警描述中有运维手册链接
- ✓在 staging 练习回滚 — staging 回不了,生产也回不了