api-design-patterns
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseAPI Design Patterns
API设计模式
Design robust, scalable APIs using proven patterns for REST, GraphQL, and gRPC with proper versioning, authentication, and error handling.
使用经过验证的REST、GraphQL和gRPC模式,结合规范的版本控制、身份验证和错误处理机制,设计健壮、可扩展的API。
Quick Reference
快速参考
API Style Selection:
- REST: Resource-based CRUD, simple clients, HTTP-native caching
- GraphQL: Client-driven queries, complex data graphs, real-time subscriptions
- gRPC: High-performance RPC, microservices, strong typing, streaming
Critical Patterns:
- Versioning: URI (), header (
/v1/users), content negotiationAccept: application/vnd.api+json;version=1 - Pagination: Offset (simple), cursor (stable), keyset (performant)
- Auth: OAuth2 (delegated), JWT (stateless), API keys (service-to-service)
- Rate limiting: Token bucket, fixed window, sliding window
- Idempotency: Idempotency keys, conditional requests, safe retry
See references/ for deep dives: , , , ,
rest-patterns.mdgraphql-patterns.mdgrpc-patterns.mdversioning-strategies.mdauthentication.mdAPI风格选择:
- REST: 基于资源的CRUD操作、简单客户端、HTTP原生缓存
- GraphQL: 客户端驱动的查询、复杂数据图谱、实时订阅
- gRPC: 高性能RPC、微服务、强类型、流处理
核心模式:
- 版本控制: URI()、请求头(
/v1/users)、内容协商Accept: application/vnd.api+json;version=1 - 分页: 偏移量(简单易实现)、游标(稳定可靠)、键集(性能优异)
- 身份验证: OAuth2(委托授权)、JWT(无状态)、API密钥(服务间通信)
- 速率限制: 令牌桶、固定窗口、滑动窗口
- 幂等性: 幂等键、条件请求、安全重试
查看references/目录获取深度内容: , , , ,
rest-patterns.mdgraphql-patterns.mdgrpc-patterns.mdversioning-strategies.mdauthentication.mdCore Principles
核心原则
Universal API Design Standards
通用API设计标准
Apply these principles across all API styles:
1. Consistency Over Cleverness
- Follow established conventions for your API style
- Use predictable naming patterns (snake_case or camelCase, pick one)
- Maintain consistent error response formats
- Version breaking changes, never surprise clients
2. Design for Evolution
- Plan for versioning from day one
- Use optional fields with sensible defaults
- Deprecate gracefully with sunset dates
- Document breaking vs non-breaking changes
3. Security by Default
- Require authentication unless explicitly public
- Use HTTPS/TLS for all production endpoints
- Implement rate limiting and throttling
- Validate and sanitize all inputs
- Return minimal error details to clients
4. Developer Experience First
- Provide comprehensive documentation (OpenAPI, GraphQL schema)
- Return meaningful error messages with actionable guidance
- Use standard HTTP status codes correctly
- Include request IDs for debugging
- Offer SDKs and code generators
以下原则适用于所有API风格:
1. 一致性优先,避免过度设计
- 遵循所选API风格的既定规范
- 使用可预测的命名模式(选择snake_case或camelCase并保持一致)
- 保持统一的错误响应格式
- 对破坏性变更进行版本标记,切勿让客户端感到意外
2. 为演进而设计
- 从项目初期就规划版本控制
- 使用带合理默认值的可选字段
- 优雅地弃用功能并设置终止支持日期
- 明确记录破坏性与非破坏性变更
3. 默认内置安全机制
- 除非明确公开,否则要求身份验证
- 所有生产环境端点均使用HTTPS/TLS
- 实现速率限制与流量削峰
- 验证并清理所有输入数据
- 向客户端返回最少的错误细节
4. 以开发者体验为核心
- 提供全面的文档(OpenAPI、GraphQL schema)
- 返回包含可操作指引的有意义错误信息
- 正确使用标准HTTP状态码
- 包含用于调试的请求ID
- 提供SDK和代码生成工具
API Style Decision Tree
API风格决策树
When to Choose REST
何时选择REST
✅ Use REST when:
- Building CRUD-focused resource APIs
- Clients need HTTP caching (ETags, Cache-Control)
- Wide platform compatibility required (browsers, mobile, IoT)
- Simple, stateless client-server model fits
- Team familiar with HTTP/REST conventions
❌ Avoid REST when:
- Complex data fetching with nested relationships (N+1 queries)
- Real-time updates are primary use case
- Need strong typing and code generation
- High-performance RPC between microservices
Example Use Cases: Public APIs, mobile backends, traditional web services
✅ 推荐使用REST的场景:
- 构建以CRUD操作为核心的资源型API
- 客户端需要HTTP缓存(ETags、Cache-Control)
- 需要广泛的平台兼容性(浏览器、移动端、IoT设备)
- 简单的无状态客户端-服务端模型适用
- 团队熟悉HTTP/REST规范
❌ 不推荐使用REST的场景:
- 涉及嵌套关系的复杂数据获取(N+1查询问题)
- 实时更新是核心需求
- 需要强类型和代码生成
- 微服务间需要高性能RPC通信
示例用例: 公共API、移动应用后端、传统Web服务
When to Choose GraphQL
何时选择GraphQL
✅ Use GraphQL when:
- Clients need flexible, client-driven queries
- Complex data graphs with nested relationships
- Multiple client types with different data needs
- Real-time subscriptions required
- Strong typing and schema validation needed
❌ Avoid GraphQL when:
- Simple CRUD operations dominate
- HTTP caching is critical (GraphQL uses POST)
- File uploads are primary feature (requires extensions)
- Team lacks GraphQL expertise
- Performance optimization is complex (N+1 problem)
Example Use Cases: Client-facing APIs, dashboards, mobile apps with varied UIs
✅ 推荐使用GraphQL的场景:
- 客户端需要灵活的、客户端驱动的查询
- 存在带嵌套关系的复杂数据图谱
- 有多类客户端且数据需求不同
- 需要实时订阅功能
- 需要强类型和Schema验证
❌ 不推荐使用GraphQL的场景:
- 以简单CRUD操作为主
- HTTP缓存至关重要(GraphQL默认使用POST)
- 文件上传是核心功能(需要扩展支持)
- 团队缺乏GraphQL相关经验
- 性能优化复杂度高(N+1问题)
示例用例: 面向客户端的API、仪表盘、UI需求多样的移动应用
When to Choose gRPC
何时选择gRPC
✅ Use gRPC when:
- Microservice-to-microservice communication
- High performance and low latency critical
- Bidirectional streaming needed
- Strong typing with Protocol Buffers
- Polyglot environments (language interop)
❌ Avoid gRPC when:
- Browser clients (limited support, needs grpc-web)
- HTTP/JSON required for compatibility
- Human-readable payloads preferred
- Simple request/response patterns
Example Use Cases: Internal microservices, streaming data, service mesh
✅ 推荐使用gRPC的场景:
- 微服务间的通信
- 对高性能和低延迟有严格要求
- 需要双向流处理
- 需使用Protocol Buffers实现强类型
- 多语言环境(跨语言交互)
❌ 不推荐使用gRPC的场景:
- 浏览器客户端(支持有限,需使用grpc-web)
- 需要HTTP/JSON兼容性
- 偏好人类可读的负载格式
- 仅需简单的请求-响应模式
示例用例: 内部微服务、流数据处理、服务网格
REST API Patterns
REST API模式
Resource Naming
资源命名
✅ Good: Plural nouns, hierarchical
GET /users # List users
GET /users/123 # Get user
POST /users # Create user
PUT /users/123 # Update user (full)
PATCH /users/123 # Update user (partial)
DELETE /users/123 # Delete user
GET /users/123/orders # User's orders (sub-resource)❌ Bad: Verbs, mixed conventions
GET /getUsers # Don't use verbs
POST /user/create # Don't use verbs
GET /Users/123 # Don't capitalize
GET /user/123 # Don't mix singular/plural✅ 规范写法:复数名词、层级结构
GET /users # 列出用户
GET /users/123 # 获取单个用户
POST /users # 创建用户
PUT /users/123 # 全量更新用户
PATCH /users/123 # 部分更新用户
DELETE /users/123 # 删除用户
GET /users/123/orders # 用户的订单(子资源)❌ 不规范写法:使用动词、混合命名规则
GET /getUsers # 请勿使用动词
POST /user/create # 请勿使用动词
GET /Users/123 # 请勿大小写混合
GET /user/123 # 请勿单复数混合HTTP Status Codes
HTTP状态码
Success Codes:
- : Successful GET, PUT, PATCH, DELETE with body
200 OK - : Successful POST, return Location header
201 Created - : Async operation started
202 Accepted - : Successful DELETE, no body
204 No Content
Client Error Codes:
- : Invalid input, validation error
400 Bad Request - : Missing or invalid authentication
401 Unauthorized - : Authenticated but insufficient permissions
403 Forbidden - : Resource doesn't exist
404 Not Found - : State conflict (duplicate, version mismatch)
409 Conflict - : Semantic validation error
422 Unprocessable Entity - : Rate limit exceeded
429 Too Many Requests
Server Error Codes:
- : Unexpected error
500 Internal Server Error - : Upstream service error
502 Bad Gateway - : Temporary outage
503 Service Unavailable - : Upstream timeout
504 Gateway Timeout
成功状态码:
- : 成功的GET、PUT、PATCH、DELETE请求,返回响应体
200 OK - : 成功的POST请求,返回Location请求头
201 Created - : 异步操作已启动
202 Accepted - : 成功的DELETE请求,无响应体
204 No Content
客户端错误状态码:
- : 输入无效、验证错误
400 Bad Request - : 缺少或无效的身份验证信息
401 Unauthorized - : 已通过身份验证但权限不足
403 Forbidden - : 资源不存在
404 Not Found - : 状态冲突(重复数据、版本不匹配)
409 Conflict - : 语义验证错误
422 Unprocessable Entity - : 超出速率限制
429 Too Many Requests
服务端错误状态码:
- : 意外错误
500 Internal Server Error - : 上游服务错误
502 Bad Gateway - : 临时服务中断
503 Service Unavailable - : 上游服务超时
504 Gateway Timeout
Error Response Format
错误响应格式
✅ Consistent error structure
json
{
"error": {
"code": "VALIDATION_ERROR",
"message": "Invalid request parameters",
"details": [
{
"field": "email",
"message": "Invalid email format",
"code": "INVALID_FORMAT"
}
],
"request_id": "req_abc123",
"documentation_url": "https://api.example.com/docs/errors/validation"
}
}✅ 统一的错误结构
json
{
"error": {
"code": "VALIDATION_ERROR",
"message": "请求参数无效",
"details": [
{
"field": "email",
"message": "邮箱格式无效",
"code": "INVALID_FORMAT"
}
],
"request_id": "req_abc123",
"documentation_url": "https://api.example.com/docs/errors/validation"
}
}Pagination Patterns
分页模式
Offset Pagination (simple, familiar):
GET /users?limit=20&offset=40✅ Use for: Small datasets, admin interfaces
❌ Avoid for: Large datasets (skips become expensive), real-time data
Cursor Pagination (stable, efficient):
GET /users?limit=20&cursor=eyJpZCI6MTIzfQ
Response: { "data": [...], "next_cursor": "eyJpZCI6MTQzfQ" }✅ Use for: Infinite scroll, real-time feeds, large datasets
❌ Avoid for: Random access, page numbers
Keyset Pagination (performant):
GET /users?limit=20&after_id=123✅ Use for: Ordered data, database index friendly
❌ Avoid for: Complex sorting, multiple sort keys
See for filtering, sorting, field selection, HATEOAS
references/rest-patterns.md偏移量分页(简单、易上手):
GET /users?limit=20&offset=40✅ 适用场景: 小型数据集、管理后台
❌ 避免场景: 大型数据集(偏移量过大会导致性能下降)、实时数据
游标分页(稳定、高效):
GET /users?limit=20&cursor=eyJpZCI6MTIzfQ
Response: { "data": [...], "next_cursor": "eyJpZCI6MTQzfQ" }✅ 适用场景: 无限滚动、实时信息流、大型数据集
❌ 避免场景: 随机访问、页码跳转
键集分页(性能优异):
GET /users?limit=20&after_id=123✅ 适用场景: 有序数据、数据库索引友好
❌ 避免场景: 复杂排序、多排序键
查看获取过滤、排序、字段选择、HATEOAS相关内容
references/rest-patterns.mdGraphQL Patterns
GraphQL模式
Schema Design
Schema设计
✅ Good: Clear types, nullable by default
graphql
type User {
id: ID! # Non-null ID
email: String! # Required field
name: String # Optional (nullable by default)
createdAt: DateTime!
orders: [Order!]! # Non-null array of non-null orders
}
type Query {
user(id: ID!): User
users(first: Int, after: String): UserConnection!
}
type Mutation {
createUser(input: CreateUserInput!): CreateUserPayload!
}
input CreateUserInput {
email: String!
name: String
}
type CreateUserPayload {
user: User
userEdge: UserEdge
errors: [UserError!]
}✅ 规范写法:清晰的类型、默认可空
graphql
type User {
id: ID! # 非空ID
email: String! # 必填字段
name: String # 可选字段(默认可空)
createdAt: DateTime!
orders: [Order!]! # 非空数组,元素为非空Order
}
type Query {
user(id: ID!): User
users(first: Int, after: String): UserConnection!
}
type Mutation {
createUser(input: CreateUserInput!): CreateUserPayload!
}
input CreateUserInput {
email: String!
name: String
}
type CreateUserPayload {
user: User
userEdge: UserEdge
errors: [UserError!]
}Resolver Patterns
解析器模式
Avoid N+1 Queries with DataLoader:
typescript
import DataLoader from 'dataloader';
const userLoader = new DataLoader(async (userIds: string[]) => {
const users = await db.users.findMany({ where: { id: { in: userIds } } });
return userIds.map(id => users.find(u => u.id === id));
});
// Resolver batches queries automatically
const resolvers = {
Order: {
user: (order) => userLoader.load(order.userId)
}
};使用DataLoader避免N+1查询:
typescript
import DataLoader from 'dataloader';
const userLoader = new DataLoader(async (userIds: string[]) => {
const users = await db.users.findMany({ where: { id: { in: userIds } } });
return userIds.map(id => users.find(u => u.id === id));
});
// 解析器自动批量处理查询
const resolvers = {
Order: {
user: (order) => userLoader.load(order.userId)
}
};Query Complexity Analysis
查询复杂度分析
Prevent expensive queries:
typescript
import { createComplexityLimitRule } from 'graphql-validation-complexity';
const server = new ApolloServer({
schema,
validationRules: [
createComplexityLimitRule(1000, {
onCost: (cost) => console.log('Query cost:', cost),
}),
],
});See for subscriptions, relay cursor connections, error handling
references/graphql-patterns.md防止资源消耗过大的查询:
typescript
import { createComplexityLimitRule } from 'graphql-validation-complexity';
const server = new ApolloServer({
schema,
validationRules: [
createComplexityLimitRule(1000, {
onCost: (cost) => console.log('查询成本:', cost),
}),
],
});查看获取订阅、Relay游标连接、错误处理相关内容
references/graphql-patterns.mdgRPC Patterns
gRPC模式
Service Definition
服务定义
protobuf
syntax = "proto3";
package users.v1;
service UserService {
rpc GetUser (GetUserRequest) returns (User) {}
rpc ListUsers (ListUsersRequest) returns (ListUsersResponse) {}
rpc CreateUser (CreateUserRequest) returns (User) {}
rpc StreamUsers (StreamUsersRequest) returns (stream User) {}
rpc BidiChat (stream ChatMessage) returns (stream ChatMessage) {}
}
message User {
string id = 1;
string email = 2;
string name = 3;
google.protobuf.Timestamp created_at = 4;
}
message GetUserRequest {
string id = 1;
}
message ListUsersRequest {
int32 page_size = 1;
string page_token = 2;
}
message ListUsersResponse {
repeated User users = 1;
string next_page_token = 2;
}protobuf
syntax = "proto3";
package users.v1;
service UserService {
rpc GetUser (GetUserRequest) returns (User) {}
rpc ListUsers (ListUsersRequest) returns (ListUsersResponse) {}
rpc CreateUser (CreateUserRequest) returns (User) {}
rpc StreamUsers (StreamUsersRequest) returns (stream User) {}
rpc BidiChat (stream ChatMessage) returns (stream ChatMessage) {}
}
message User {
string id = 1;
string email = 2;
string name = 3;
google.protobuf.Timestamp created_at = 4;
}
message GetUserRequest {
string id = 1;
}
message ListUsersRequest {
int32 page_size = 1;
string page_token = 2;
}
message ListUsersResponse {
repeated User users = 1;
string next_page_token = 2;
}Error Handling
错误处理
go
import (
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
)
func (s *server) GetUser(ctx context.Context, req *pb.GetUserRequest) (*pb.User, error) {
if req.Id == "" {
return nil, status.Error(codes.InvalidArgument, "user ID is required")
}
user, err := s.db.GetUser(ctx, req.Id)
if err != nil {
if errors.Is(err, sql.ErrNoRows) {
return nil, status.Error(codes.NotFound, "user not found")
}
return nil, status.Error(codes.Internal, "database error")
}
return user, nil
}See for streaming, interceptors, metadata, health checks
references/grpc-patterns.mdgo
import (
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
)
func (s *server) GetUser(ctx context.Context, req *pb.GetUserRequest) (*pb.User, error) {
if req.Id == "" {
return nil, status.Error(codes.InvalidArgument, "用户ID为必填项")
}
user, err := s.db.GetUser(ctx, req.Id)
if err != nil {
if errors.Is(err, sql.ErrNoRows) {
return nil, status.Error(codes.NotFound, "用户不存在")
}
return nil, status.Error(codes.Internal, "数据库错误")
}
return user, nil
}查看获取流处理、拦截器、元数据、健康检查相关内容
references/grpc-patterns.mdVersioning Strategies
版本控制策略
URI Versioning (Simple, Explicit)
URI版本控制(简单、明确)
✅ Most common, easy to understand
GET /v1/users/123
GET /v2/users/123Pros: Clear, easy to route, browser-friendly
Cons: Couples version to URL, duplicates routes
✅ 最常用、易理解
GET /v1/users/123
GET /v2/users/123优点: 清晰明了、路由简单、浏览器友好
缺点: 版本与URL耦合、路由重复
Header Versioning (Clean URLs)
请求头版本控制(URL更简洁)
GET /users/123
Accept: application/vnd.myapi.v2+jsonPros: Clean URLs, version separate from resource
Cons: Less visible, harder to test manually
GET /users/123
Accept: application/vnd.myapi.v2+json优点: URL简洁、版本与资源分离
缺点: 可见性低、手动测试较难
Content Negotiation (Granular)
内容协商(粒度更细)
GET /users/123
Accept: application/vnd.myapi.user.v2+jsonPros: Resource-level versioning, backward compatible
Cons: Complex, harder to implement
GET /users/123
Accept: application/vnd.myapi.user.v2+json优点: 资源级版本控制、向后兼容
缺点: 复杂度高、实现难度大
Version Deprecation Process
版本弃用流程
json
{
"version": "1.0",
"deprecated": true,
"sunset_date": "2025-12-31",
"migration_guide": "https://docs.api.com/v1-to-v2",
"replacement_version": "2.0"
}Include deprecation warnings:
HTTP/1.1 200 OK
Deprecation: true
Sunset: Sat, 31 Dec 2025 23:59:59 GMT
Link: <https://docs.api.com/v1-to-v2>; rel="deprecation"See for detailed migration patterns
references/versioning-strategies.mdjson
{
"version": "1.0",
"deprecated": true,
"sunset_date": "2025-12-31",
"migration_guide": "https://docs.api.com/v1-to-v2",
"replacement_version": "2.0"
}包含弃用警告:
HTTP/1.1 200 OK
Deprecation: true
Sunset: Sat, 31 Dec 2025 23:59:59 GMT
Link: <https://docs.api.com/v1-to-v2>; rel="deprecation"查看获取详细的迁移模式
references/versioning-strategies.mdAuthentication & Authorization
身份验证与授权
OAuth 2.0 (Delegated Access)
OAuth 2.0(委托访问)
Use for: Third-party access, user consent, token refresh
Authorization Code Flow (most secure for web/mobile):
1. Client redirects to /authorize
2. User authenticates, grants permissions
3. Auth server redirects to callback with code
4. Client exchanges code for access token
5. Client uses access token for API requestshttp
undefined适用场景: 第三方访问、用户授权、令牌刷新
授权码流程(Web/移动端最安全):
1. 客户端重定向到/authorize
2. 用户完成身份验证并授予权限
3. 认证服务器重定向到回调地址并携带授权码
4. 客户端使用授权码交换访问令牌
5. 客户端使用访问令牌调用APIhttp
undefinedRequest token
请求令牌
POST /oauth/token
Content-Type: application/x-www-form-urlencoded
grant_type=authorization_code
&code=AUTH_CODE
&redirect_uri=https://client.com/callback
&client_id=CLIENT_ID
&client_secret=CLIENT_SECRET
POST /oauth/token
Content-Type: application/x-www-form-urlencoded
grant_type=authorization_code
&code=AUTH_CODE
&redirect_uri=https://client.com/callback
&client_id=CLIENT_ID
&client_secret=CLIENT_SECRET
Response
响应
{
"access_token": "eyJhbGc...",
"token_type": "Bearer",
"expires_in": 3600,
"refresh_token": "tGzv3JOkF0XG5Qx2TlKWIA",
"scope": "read write"
}
{
"access_token": "eyJhbGc...",
"token_type": "Bearer",
"expires_in": 3600,
"refresh_token": "tGzv3JOkF0XG5Qx2TlKWIA",
"scope": "read write"
}
Use token
使用令牌
GET /v1/users/me
Authorization: Bearer eyJhbGc...
undefinedGET /v1/users/me
Authorization: Bearer eyJhbGc...
undefinedJWT (Stateless Auth)
JWT(无状态身份验证)
Use for: Microservices, stateless API auth, short-lived tokens
✅ Good: Minimal claims, short expiry
json
{
"sub": "user_123",
"iat": 1516239022,
"exp": 1516242622,
"scope": "read:users write:orders"
}Validation:
typescript
import jwt from 'jsonwebtoken';
const token = req.headers.authorization?.split(' ')[1];
const payload = jwt.verify(token, process.env.JWT_SECRET);
req.userId = payload.sub;适用场景: 微服务、无状态API身份验证、短期令牌
✅ 规范写法:最小化声明、短有效期
json
{
"sub": "user_123",
"iat": 1516239022,
"exp": 1516242622,
"scope": "read:users write:orders"
}验证逻辑:
typescript
import jwt from 'jsonwebtoken';
const token = req.headers.authorization?.split(' ')[1];
const payload = jwt.verify(token, process.env.JWT_SECRET);
req.userId = payload.sub;API Keys (Service-to-Service)
API密钥(服务间通信)
Use for: Server-to-server, CLI tools, webhooks
http
GET /v1/users
X-API-Key: sk_live_abc123...适用场景: 服务器间通信、CLI工具、Webhook
http
GET /v1/users
X-API-Key: sk_live_abc123...Or query parameter (less secure)
或查询参数(安全性较低)
GET /v1/users?api_key=sk_live_abc123
**Key Practices**:
- Prefix keys with environment (`sk_live_`, `sk_test_`)
- Hash keys before storage (bcrypt, scrypt)
- Allow key rotation without downtime
- Support multiple keys per user
- Rate limit per key
See `references/authentication.md` for API key rotation, scopes, RBACGET /v1/users?api_key=sk_live_abc123
**最佳实践**:
- 密钥前缀区分环境(`sk_live_`、`sk_test_`)
- 存储前对密钥进行哈希处理(bcrypt、scrypt)
- 支持无停机密钥轮换
- 允许每个用户拥有多个密钥
- 按密钥进行速率限制
查看`references/authentication.md`获取API密钥轮换、权限范围、RBAC相关内容Rate Limiting
速率限制
Token Bucket (Burst-Friendly)
令牌桶算法(支持突发流量)
Bucket: 100 tokens, refill 10/second
Request costs 1 token
Allows bursts up to bucket sizeHeaders:
http
HTTP/1.1 200 OK
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 73
X-RateLimit-Reset: 1640995200429 Response:
http
HTTP/1.1 429 Too Many Requests
Retry-After: 60
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1640995200
{
"error": {
"code": "RATE_LIMIT_EXCEEDED",
"message": "Rate limit exceeded. Try again in 60 seconds.",
"limit": 100,
"reset_at": "2025-01-01T00:00:00Z"
}
}令牌桶: 100个令牌,每秒补充10个
每个请求消耗1个令牌
允许最多突发令牌桶容量的请求响应头:
http
HTTP/1.1 200 OK
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 73
X-RateLimit-Reset: 1640995200429响应:
http
HTTP/1.1 429 Too Many Requests
Retry-After: 60
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1640995200
{
"error": {
"code": "RATE_LIMIT_EXCEEDED",
"message": "超出速率限制,请60秒后重试。",
"limit": 100,
"reset_at": "2025-01-01T00:00:00Z"
}
}Sliding Window (Fair Distribution)
滑动窗口算法(公平分配)
Counts requests in rolling time window. More accurate than fixed window.
在滚动时间窗口内统计请求数,比固定窗口算法更准确。
Per-User vs Per-IP
按用户 vs 按IP限制
- Per-User: Authenticated requests, fair quotas
- Per-IP: Unauthenticated requests, prevent abuse
- Combined: Both limits, take stricter
- 按用户: 已认证请求,公平配额
- 按IP: 未认证请求,防止滥用
- 组合使用: 同时应用两种限制,取更严格的规则
Idempotency
幂等性
Idempotent Methods (HTTP Spec)
幂等方法(HTTP规范)
Naturally Idempotent: GET, PUT, DELETE, HEAD, OPTIONS
Not Idempotent: POST, PATCH
天然幂等: GET、PUT、DELETE、HEAD、OPTIONS
非幂等: POST、PATCH
Idempotency Keys
幂等键
Make POST requests idempotent:
http
POST /v1/payments
Idempotency-Key: uuid-or-client-generated-key
Content-Type: application/json
{
"amount": 1000,
"currency": "USD",
"customer": "cust_123"
}Server behavior:
- First request: Process and store result with key
- Duplicate request (same key): Return stored result (200 or 201)
- Different request (same key): Return 409 Conflict
Implementation:
typescript
const idempotencyKey = req.headers['idempotency-key'];
if (idempotencyKey) {
const cached = await redis.get(`idempotency:${idempotencyKey}`);
if (cached) {
return res.status(cached.status).json(cached.body);
}
}
const result = await processPayment(req.body);
await redis.setex(`idempotency:${idempotencyKey}`, 86400, {
status: 201,
body: result
});使POST请求具备幂等性:
http
POST /v1/payments
Idempotency-Key: uuid-or-client-generated-key
Content-Type: application/json
{
"amount": 1000,
"currency": "USD",
"customer": "cust_123"
}服务端行为:
- 首次请求: 处理请求并存储结果与对应键
- 重复请求(相同键): 返回存储的结果(200或201状态码)
- 不同请求(相同键): 返回409 Conflict
实现示例:
typescript
const idempotencyKey = req.headers['idempotency-key'];
if (idempotencyKey) {
const cached = await redis.get(`idempotency:${idempotencyKey}`);
if (cached) {
return res.status(cached.status).json(cached.body);
}
}
const result = await processPayment(req.body);
await redis.setex(`idempotency:${idempotencyKey}`, 86400, {
status: 201,
body: result
});Conditional Requests
条件请求
Use ETags for safe updates:
http
undefined使用ETags实现安全更新:
http
undefinedGet resource with ETag
获取资源及对应的ETag
GET /v1/users/123
Response: ETag: "abc123"
GET /v1/users/123
Response: ETag: "abc123"
Update only if unchanged
仅在资源未变更时更新
PUT /v1/users/123
If-Match: "abc123"
PUT /v1/users/123
If-Match: "abc123"
412 Precondition Failed if ETag changed
若ETag已变更,返回412 Precondition Failed
undefinedundefinedCaching Strategies
缓存策略
HTTP Caching Headers
HTTP缓存头
http
undefinedhttp
undefinedPublic, cacheable for 1 hour
公共缓存,有效期1小时
Cache-Control: public, max-age=3600
Cache-Control: public, max-age=3600
Private (user-specific), revalidate
私有缓存(用户专属),需重新验证
Cache-Control: private, must-revalidate, max-age=0
Cache-Control: private, must-revalidate, max-age=0
No caching
禁止缓存
Cache-Control: no-store, no-cache, must-revalidate
undefinedCache-Control: no-store, no-cache, must-revalidate
undefinedETag Validation
ETag验证
http
undefinedhttp
undefinedServer returns ETag
服务端返回ETag
GET /v1/users/123
Response:
ETag: "33a64df551425fcc55e4d42a148795d9f25f89d4"
Cache-Control: max-age=3600
GET /v1/users/123
Response:
ETag: "33a64df551425fcc55e4d42a148795d9f25f89d4"
Cache-Control: max-age=3600
Client conditional request
客户端发起条件请求
GET /v1/users/123
If-None-Match: "33a64df551425fcc55e4d42a148795d9f25f89d4"
GET /v1/users/123
If-None-Match: "33a64df551425fcc55e4d42a148795d9f25f89d4"
304 Not Modified if unchanged (saves bandwidth)
若资源未变更,返回304 Not Modified(节省带宽)
HTTP/1.1 304 Not Modified
undefinedHTTP/1.1 304 Not Modified
undefinedLast-Modified
Last-Modified
http
GET /v1/users/123
Response:
Last-Modified: Wed, 21 Oct 2025 07:28:00 GMThttp
GET /v1/users/123
Response:
Last-Modified: Wed, 21 Oct 2025 07:28:00 GMTConditional request
客户端发起条件请求
GET /v1/users/123
If-Modified-Since: Wed, 21 Oct 2025 07:28:00 GMT
GET /v1/users/123
If-Modified-Since: Wed, 21 Oct 2025 07:28:00 GMT
304 Not Modified if not modified
若资源未变更,返回304 Not Modified
undefinedundefinedWebhooks
Webhook
Event Delivery
事件投递
http
POST https://client.com/webhooks/payments
Content-Type: application/json
X-Webhook-Signature: sha256=abc123...
X-Webhook-Id: evt_abc123
X-Webhook-Timestamp: 1640995200
{
"id": "evt_abc123",
"type": "payment.succeeded",
"created": 1640995200,
"data": {
"object": {
"id": "pay_123",
"amount": 1000,
"status": "succeeded"
}
}
}http
POST https://client.com/webhooks/payments
Content-Type: application/json
X-Webhook-Signature: sha256=abc123...
X-Webhook-Id: evt_abc123
X-Webhook-Timestamp: 1640995200
{
"id": "evt_abc123",
"type": "payment.succeeded",
"created": 1640995200,
"data": {
"object": {
"id": "pay_123",
"amount": 1000,
"status": "succeeded"
}
}
}Signature Verification
签名验证
typescript
import crypto from 'crypto';
function verifyWebhookSignature(
payload: string,
signature: string,
secret: string
): boolean {
const expectedSignature = crypto
.createHmac('sha256', secret)
.update(payload)
.digest('hex');
return crypto.timingSafeEqual(
Buffer.from(signature),
Buffer.from(`sha256=${expectedSignature}`)
);
}typescript
import crypto from 'crypto';
function verifyWebhookSignature(
payload: string,
signature: string,
secret: string
): boolean {
const expectedSignature = crypto
.createHmac('sha256', secret)
.update(payload)
.digest('hex');
return crypto.timingSafeEqual(
Buffer.from(signature),
Buffer.from(`sha256=${expectedSignature}`)
);
}Retry Strategy
重试策略
- Exponential backoff: 1s, 2s, 4s, 8s, 16s, 32s, 64s
- Timeout: 5-30 seconds per attempt
- Max attempts: 3-7 attempts
- Dead letter queue: Store failed events
- Manual retry: UI for re-sending failed events
- 指数退避: 1s、2s、4s、8s、16s、32s、64s
- 超时时间: 每次尝试5-30秒
- 最大尝试次数: 3-7次
- 死信队列: 存储投递失败的事件
- 手动重试: 提供UI用于重新发送失败事件
API Documentation
API文档
OpenAPI/Swagger (REST)
OpenAPI/Swagger(REST)
yaml
openapi: 3.0.0
info:
title: User API
version: 1.0.0
paths:
/users/{id}:
get:
summary: Get user by ID
parameters:
- name: id
in: path
required: true
schema:
type: string
responses:
'200':
description: Successful response
content:
application/json:
schema:
$ref: '#/components/schemas/User'
'404':
description: User not found
components:
schemas:
User:
type: object
required: [id, email]
properties:
id:
type: string
email:
type: string
format: email
name:
type: stringyaml
openapi: 3.0.0
info:
title: 用户API
version: 1.0.0
paths:
/users/{id}:
get:
summary: 根据ID获取用户
parameters:
- name: id
in: path
required: true
schema:
type: string
responses:
'200':
description: 成功响应
content:
application/json:
schema:
$ref: '#/components/schemas/User'
'404':
description: 用户不存在
components:
schemas:
User:
type: object
required: [id, email]
properties:
id:
type: string
email:
type: string
format: email
name:
type: stringGraphQL Schema (Self-Documenting)
GraphQL Schema(自文档化)
GraphQL introspection provides automatic documentation. Use descriptions:
graphql
"""
Represents a user account in the system.
Created via the createUser mutation.
"""
type User {
"""Unique identifier for the user"""
id: ID!
"""Email address, must be unique"""
email: String!
"""Optional display name"""
name: String
}GraphQL自省功能可自动生成文档,建议添加描述:
graphql
"""
代表系统中的用户账户。
通过createUser mutation创建。
"""
type User {
"""用户的唯一标识符"""
id: ID!
"""邮箱地址,必须唯一"""
email: String!
"""可选的显示名称"""
name: String
}API Documentation Best Practices
API文档最佳实践
- Interactive examples: Provide working code samples
- Authentication guide: Step-by-step auth setup
- Error catalog: Document all error codes with examples
- Rate limits: Clearly state limits and headers
- Changelog: Track breaking and non-breaking changes
- Migration guides: Version upgrade instructions
- SDKs: Provide client libraries for popular languages
- 交互式示例: 提供可运行的代码示例
- 身份验证指南: 分步的身份验证设置说明
- 错误目录: 记录所有错误码及示例
- 速率限制: 明确说明限制规则及响应头
- 变更日志: 跟踪破坏性与非破坏性变更
- 迁移指南: 版本升级说明
- SDK: 提供主流语言的客户端库
Anti-Patterns
反模式
❌ Over-fetching (REST): Returning entire objects when fields are unused
✅ Solution: Support field selection ()
?fields=id,name,email❌ Under-fetching (REST): Requiring multiple requests for related data
✅ Solution: Support expansion () or use GraphQL
?expand=orders,profile❌ Chatty APIs: Too many round-trips for common operations
✅ Solution: Batch endpoints, compound documents, or GraphQL
❌ Ignoring HTTP semantics: Using GET for mutations, wrong status codes
✅ Solution: Follow HTTP spec, use correct methods and status codes
❌ Exposing internal structure: URLs/schemas mirror database
✅ Solution: Design resource-oriented APIs independent of storage
❌ Missing versioning: Breaking changes without version increments
✅ Solution: Version from day one, never break existing versions
❌ Poor error messages: Generic "An error occurred"
✅ Solution: Specific, actionable error messages with codes
❌ No rate limiting: APIs vulnerable to abuse
✅ Solution: Implement rate limiting from the start
❌ 过度获取(REST): 返回未使用的完整对象字段
✅ 解决方案: 支持字段选择()
?fields=id,name,email❌ 获取不足(REST): 需多次请求获取关联数据
✅ 解决方案: 支持关联数据扩展()或使用GraphQL
?expand=orders,profile❌ 聊天式API: 常见操作需过多往返请求
✅ 解决方案: 提供批量端点、复合文档或使用GraphQL
❌ 忽略HTTP语义: 使用GET执行变更、错误使用状态码
✅ 解决方案: 遵循HTTP规范,使用正确的方法和状态码
❌ 暴露内部结构: URL/Schema直接映射数据库结构
✅ 解决方案: 设计与存储无关的资源型API
❌ 缺少版本控制: 引入破坏性变更但未升级版本
✅ 解决方案: 从项目初期就进行版本控制,绝不破坏现有版本
❌ 错误信息质量差: 通用的"发生了一个错误"
✅ 解决方案: 返回具体、可操作的错误信息及错误码
❌ 未实现速率限制: API易受滥用
✅ 解决方案: 从项目初期就实现速率限制
Testing Strategies
测试策略
Contract Testing
契约测试
typescript
// Pact contract test
import { PactV3 } from '@pact-foundation/pact';
const provider = new PactV3({
consumer: 'FrontendApp',
provider: 'UserAPI'
});
it('gets a user by ID', () => {
provider
.given('user 123 exists')
.uponReceiving('a request for user 123')
.withRequest({
method: 'GET',
path: '/users/123'
})
.willRespondWith({
status: 200,
body: { id: '123', email: 'user@example.com' }
});
});typescript
// Pact契约测试
import { PactV3 } from '@pact-foundation/pact';
const provider = new PactV3({
consumer: 'FrontendApp',
provider: 'UserAPI'
});
it('根据ID获取用户', () => {
provider
.given('用户123存在')
.uponReceiving('获取用户123的请求')
.withRequest({
method: 'GET',
path: '/users/123'
})
.willRespondWith({
status: 200,
body: { id: '123', email: 'user@example.com' }
});
});Load Testing
负载测试
javascript
// k6 load test
import http from 'k6/http';
import { check } from 'k6';
export const options = {
stages: [
{ duration: '30s', target: 20 },
{ duration: '1m', target: 20 },
{ duration: '10s', target: 0 }
],
thresholds: {
http_req_duration: ['p(95)<500'], // 95% under 500ms
http_req_failed: ['rate<0.01'] // <1% errors
}
};
export default function () {
const res = http.get('https://api.example.com/users');
check(res, {
'status is 200': (r) => r.status === 200,
'response time < 500ms': (r) => r.timings.duration < 500
});
}javascript
// k6负载测试
import http from 'k6/http';
import { check } from 'k6';
export const options = {
stages: [
{ duration: '30s', target: 20 },
{ duration: '1m', target: 20 },
{ duration: '10s', target: 0 }
],
thresholds: {
http_req_duration: ['p(95)<500'], // 95%的请求响应时间小于500ms
http_req_failed: ['rate<0.01'] // 请求失败率小于1%
}
};
export default function () {
const res = http.get('https://api.example.com/users');
check(res, {
'状态码为200': (r) => r.status === 200,
'响应时间小于500ms': (r) => r.timings.duration < 500
});
}Related Skills
相关技能
- graphql: Deep GraphQL schema design, resolvers, Apollo Server
- typescript: Type-safe API clients and servers
- nodejs-backend: Express/Fastify REST API implementation
- django: Django REST Framework patterns
- fastapi: FastAPI Python REST/GraphQL APIs
- flask: Flask-RESTful patterns
- graphql: 深度GraphQL Schema设计、解析器、Apollo Server
- typescript: 类型安全的API客户端与服务端
- nodejs-backend: Express/Fastify REST API实现
- django: Django REST Framework模式
- fastapi: FastAPI Python REST/GraphQL API
- flask: Flask-RESTful模式
References
参考文档
- rest-patterns.md: Deep REST coverage (HATEOAS, filtering, field selection)
- graphql-patterns.md: GraphQL subscriptions, relay cursor connections, federation
- grpc-patterns.md: Streaming patterns, interceptors, service mesh integration
- versioning-strategies.md: Detailed versioning approaches and migration patterns
- authentication.md: OAuth flows, JWT best practices, API key rotation, RBAC
- rest-patterns.md: 深度REST内容(HATEOAS、过滤、字段选择)
- graphql-patterns.md: GraphQL订阅、Relay游标连接、联邦
- grpc-patterns.md: 流处理模式、拦截器、服务网格集成
- versioning-strategies.md: 详细的版本控制方法和迁移模式
- authentication.md: OAuth流程、JWT最佳实践、API密钥轮换、RBAC
Additional Resources
额外资源
- REST API Design Rulebook - O'Reilly REST guide
- GraphQL Best Practices - Official GraphQL guide
- gRPC Best Practices - Official gRPC guide
- RFC 7807: Problem Details for HTTP APIs - Standard error format
- OpenAPI Specification - REST documentation standard
- REST API Design Rulebook - O'Reilly REST指南
- GraphQL Best Practices - 官方GraphQL指南
- gRPC Best Practices - 官方gRPC指南
- RFC 7807: Problem Details for HTTP APIs - HTTP API标准错误格式
- OpenAPI Specification - REST文档标准