关于 api-design-ops
API Design Ops 是面向生产级 API 开发者的设计参考技能,涵盖 REST、gRPC 和 GraphQL。它结合风格选择指南与错误处理、分页、安全和版本管理等常见问题的设计模式。
主要功能
- 01REST、gRPC 和 GraphQL 指南
- 02API 风格选择框架
- 03资源建模与分页模式
- 04错误处理与版本管理指南
- 05身份验证和限流模式
- 06安全与性能参考资料
适用场景
- 01为服务选择 API 风格
- 02设计统一的错误响应
- 03规划分页与版本管理
- 04评估 API 安全方案
安装
复制下面的命令,在终端中运行。
$
npx skills add https:/ / github.com/ 0xdarkmatter/ claude-mods --skill api-design-ops安装后在 Claude Code 中输入/api-design-ops确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
assets/.gitkeepreferences/api-security.mdgraphql.mdgrpc.mdrest-advanced.mdscripts/.gitkeep详细介绍
API 设计运维
全面介绍 REST(进阶)、gRPC 和 GraphQL 的 API 设计模式。本技能提供决策框架、设计模式和实施指南,助力构建适用于生产环境的 API。
API 风格决策树
What kind of API do you need?
|
+-- Internal microservice-to-microservice?
| +-- High throughput, low latency needed? --> gRPC
| +-- Streaming (real-time data, logs)? --> gRPC (bidirectional streaming)
| +-- Simple request/response, team comfort? --> REST
|
+-- Public-facing API?
| +-- Third-party developers consuming it? --> REST (widest compatibility)
| +-- Mobile app with varied data needs? --> GraphQL
| +-- Browser-only, simple CRUD? --> REST
|
+-- Frontend for your own app?
| +-- Multiple clients with different data shapes? --> GraphQL
| +-- Single client, straightforward data? --> REST
| +-- Real-time updates needed? --> GraphQL subscriptions or SSE
|
+-- IoT / embedded / constrained devices?
| +-- Binary efficiency matters? --> gRPC
| +-- HTTP-only environments? --> REST
快速比较
| 关注点 | REST | gRPC | GraphQL |
|---|---|---|---|
| 传输方式 | HTTP/1.1+ | HTTP/2 | HTTP(任意) |
| 序列化 | JSON(文本) | Protobuf(二进制) | JSON(文本) |
| Schema | OpenAPI(可选) | .proto(必需) | SDL(必需) |
| 浏览器支持 | 原生 | 通过 gRPC-Web/Connect | 原生 |
| 缓存 | 内置 HTTP 缓存 | 自定义 | 自定义(规范化) |
| 学习曲线 | 低 | 中 | 中高 |
| 代码生成 | 可选 | 必需 | 可选,但推荐 |
| 流式传输 | SSE、WebSocket | 原生(4 种模式) | 订阅 |
| 过度获取 | 常见问题 | 不会(强类型) | 通过设计解决 |
| 文件上传 | 原生支持 Multipart | 分块流式传输 | Multipart 规范(不够理想) |
REST 资源设计速查
资源命名
GET /users # Collection
GET /users/{id} # Singleton
GET /users/{id}/orders # Sub-collection
POST /users # Create
PUT /users/{id} # Full replace
PATCH /users/{id} # Partial update
DELETE /users/{id} # Remove
# Naming rules:
# - Plural nouns for collections: /users NOT /user
# - Kebab-case for multi-word: /line-items NOT /lineItems
# - No verbs in URLs: POST /orders NOT POST /create-order
# - Max 3 levels deep: /users/{id}/orders (not /users/{id}/orders/{oid}/items/{iid}/details)
HTTP 方法和状态码
| 方法 | 成功 | 空 | 无效 | 未找到 | 冲突 |
|---|---|---|---|---|---|
| GET | 200 | 200(空数组) | 400 | 404 | - |
| POST | 201 + Location | - | 400/422 | - | 409 |
| PUT | 200 | - | 400/422 | 404 | 409 |
| PATCH | 200 | - | 400/422 | 404 | 409 |
| DELETE | 204 | 204(已不存在) | 400 | 404 | 409 |
HATEOAS(何时值得采用)
适用于:重视可发现性的公共 API、长期运行的 API、频繁演进的 API。 不适用于:内部微服务、移动端后端、可接受紧耦合的场景。
{
"id": "order-123",
"status": "shipped",
"_links": {
"self": { "href": "/orders/order-123" },
"track": { "href": "/orders/order-123/tracking" },
"cancel": { "href": "/orders/order-123", "method": "DELETE" }
}
}
分页决策树
What's your data like?
|
+-- Stable data, UI needs "jump to page 5"?
| --> Offset pagination: ?page=5&per_page=20
| Tradeoff: Slow on large offsets (OFFSET 10000), inconsistent with inserts
|
+-- Large dataset, forward-only traversal?
| --> Cursor pagination: ?after=eyJpZCI6MTIzfQ&limit=20
| Tradeoff: No random page access, but consistent and fast
|
+-- Real-time feed, ordered by timestamp or ID?
| --> Keyset pagination: ?created_after=2024-01-01T00:00:00Z&limit=20
| Tradeoff: Requires a unique, sequential column; no page jumping
响应封装
{
"data": [...],
"pagination": {
"total": 1432,
"limit": 20,
"has_more": true,
"next_cursor": "eyJpZCI6MTQzMn0="
}
}
错误响应格式(RFC 7807)
所有 API 都应使用 Problem Details(RFC 7807 / RFC 9457):
{
"type": "https://api.example.com/errors/insufficient-funds",
"title": "Insufficient Funds",
"status": 422,
"detail": "Account xxxx-1234 has a balance of $10.00, but the transfer requires $25.00.",
"instance": "/transfers/txn-abc-123",
"balance": 1000,
"required": 2500
}
字段参考
| 字段 | 必需 | 说明 |
|---|---|---|
type | 是 | 用于标识错误类型的 URI(稳定且可记录) |
title | 是 | 面向用户的摘要(此类型的所有实例均相同) |
status | 是 | HTTP 状态码 |
detail | 是 | 针对此次错误的面向用户说明 |
instance | 否 | 用于标识具体错误实例的 URI |
| (扩展字段) | 否 | 其他机器可读字段 |
验证错误
{
"type": "https://api.example.com/errors/validation",
"title": "Validation Failed",
"status": 422,
"detail": "The request body contains 2 validation errors.",
"errors": [
{ "field": "email", "message": "Must be a valid email address", "code": "invalid_format" },
{ "field": "age", "message": "Must be at least 18", "code": "out_of_range", "min": 18 }
]
}
版本控制策略
| 策略 | 示例 | 优点 | 缺点 |
|---|---|---|---|
| URL 路径 | /v2/users | 清晰、可缓存、路由简单 | URL 冗杂,难以停用 |
| Accept 标头 | Accept: application/vnd.api.v2+json | URL 简洁,支持内容协商 | 不明显,较难测试 |
| 查询参数 | /users?version=2 | 易于添加 | 污染查询字符串,存在缓存问题 |
| 基于日期 | API-Version: 2024-01-15 | 可进行细粒度演进(Stripe 风格) | 实施复杂 |
建议
- 公共 API:采用 URL 路径版本控制(
/v1/)——简单至上 - 内部 API:使用标头或不进行版本控制(同步部署)
- 持续演进的 API:如果有相应的工程投入,可采用基于日期的版本控制(Stripe 模式)
破坏性变更规则
凡可能导致现有客户端失效的变更,均属于破坏性变更:
- 从响应中删除字段
- 重命名字段
- 更改字段类型
- 在请求中添加必填字段
- 更改 URL 结构
- 更改错误格式
- 删除端点
非破坏性(安全)变更:
- 在请求中添加可选字段
- 在响应中添加字段
- 添加新端点
- 添加新的枚举值(前提是客户端能够处理未知值)
速率限制设计
算法
| 算法 | 行为 | 适用场景 |
|---|---|---|
| 令牌桶 | 允许突发请求,以稳定速率补充令牌 | 通用 API 速率限制 |
| 滑动窗口 | 分布平滑,不允许突发 | 需要严格公平性 |
| 固定窗口 | 简单,但可能在窗口边界出现突发 | 低风险限制 |
| 漏桶 | 恒定输出速率 | 队列处理 |
响应头
X-RateLimit-Limit: 1000 # Max requests per window
X-RateLimit-Remaining: 743 # Requests left in current window
X-RateLimit-Reset: 1672531200 # Unix timestamp when window resets
Retry-After: 30 # Seconds to wait (on 429)
429 响应正文
{
"type": "https://api.example.com/errors/rate-limit-exceeded",
"title": "Rate Limit Exceeded",
"status": 429,
"detail": "You have exceeded 1000 requests per hour. Try again in 30 seconds.",
"retry_after": 30
}
幂等性
哪些方法需要幂等键?
| 方法 | 按规范是否幂等? | 是否需要键? |
|---|---|---|
| GET | 是 | 否 |
| PUT | 是 | 否(完整替换天然具有幂等性) |
| DELETE | 是 | 否 |
| PATCH | 否 | 关键操作建议使用 |
| POST | 否 | 是(支付、订单和转账始终需要) |
实现
POST /payments
Idempotency-Key: 550e8400-e29b-41d4-a716-446655440000
Content-Type: application/json
{ "amount": 2500, "currency": "usd", "customer": "cust_123" }
服务端:
- 接收带有
Idempotency-Key请求头的请求 - 检查存储中(Redis、DB)是否存在该键
- 如果存在:返回已存储的响应(相同的状态码 + 正文)
- 如果不存在:处理请求,并以幂等键为键存储响应
- 键在 24-48 小时后过期
身份验证概览
| 方法 | 使用场景 | 安全级别 |
|---|---|---|
| API Key | 服务间通信、内部场景、简单场景 | 低-中 |
| JWT (Bearer) | 无状态身份验证、微服务 | 中-高 |
| OAuth2 + PKCE | 第三方访问、用户委托 | 高 |
| mTLS | 服务网格、零信任基础设施 | 非常高 |
决策指南
Who is authenticating?
|
+-- Your own frontend? --> JWT (short-lived access + refresh token)
+-- Third-party developer? --> OAuth2 (client credentials for server, PKCE for SPA)
+-- Another internal service? --> mTLS or JWT with service accounts
+-- Quick prototype? --> API key (but plan migration)
常见问题表
| 常见问题 | 问题 | 预防措施 |
|---|---|---|
| “非破坏性”版本中的破坏性变更 | 客户端崩溃 | 仅允许增量变更的策略、契约测试 |
| REST API 中的 N+1 问题 | 100 个用户 = 101 次查询 | 复合文档、?include= 或 GraphQL |
| 过度获取 | 移动端获取 50 个字段,但只需要 3 个 | 稀疏字段集 ?fields=id,name 或 GraphQL |
| 获取不足 | 构建一个视图需要 3 次请求 | 复合端点或 BFF 模式 |
| CORS 配置错误 | 前端无法访问 API | 明确指定允许的源,携带凭据时绝不使用 * |
| 缺少 Content-Type | 415 或解析静默失败 | 在每个变更端点上验证 Content-Type |
| 大型负载未分页 | OOM、超时 | 始终对集合分页,并设置最大页面大小 |
| 日期格式不一致 | 解析混乱 | 全面使用 ISO 8601:2024-01-15T10:30:00Z |
| 没有请求 ID | 无法调试 | 生成 X-Request-ID,并在服务间传递 |
| 枚举演进 | 新值导致旧客户端出错 | 说明枚举可能增加值,客户端必须处理未知值 |
| 缺少幂等性 | 重复扣款、重复创建订单 | 所有有副作用的 POST 端点都使用幂等键 |
| 查询复杂度不受限 | GraphQL DoS | 限制深度、分析成本、使用持久化查询 |
参考文件
| 文件 | 内容 |
|---|---|
references/rest-advanced.md | 资源建模、PATCH 策略、缓存、webhook、批量操作 |
references/grpc.md | Protobuf、服务定义、Go/Rust、流式传输、错误处理 |
references/graphql.md | Schema 设计、解析器、DataLoader、联邦、性能 |
references/api-security.md | JWT、OAuth2、CORS、速率限制、OWASP API Top 10 |
评论
支持 Markdown