api-design-patterns

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

API 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 (
    /v1/users
    ), header (
    Accept: application/vnd.api+json;version=1
    ), content negotiation
  • 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.md
,
graphql-patterns.md
,
grpc-patterns.md
,
versioning-strategies.md
,
authentication.md
API风格选择:
  • REST: 基于资源的CRUD操作、简单客户端、HTTP原生缓存
  • GraphQL: 客户端驱动的查询、复杂数据图谱、实时订阅
  • gRPC: 高性能RPC、微服务、强类型、流处理
核心模式:
  • 版本控制: URI(
    /v1/users
    )、请求头(
    Accept: application/vnd.api+json;version=1
    )、内容协商
  • 分页: 偏移量(简单易实现)、游标(稳定可靠)、键集(性能优异)
  • 身份验证: OAuth2(委托授权)、JWT(无状态)、API密钥(服务间通信)
  • 速率限制: 令牌桶、固定窗口、滑动窗口
  • 幂等性: 幂等键、条件请求、安全重试
查看references/目录获取深度内容:
rest-patterns.md
,
graphql-patterns.md
,
grpc-patterns.md
,
versioning-strategies.md
,
authentication.md

Core 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:
  • 200 OK
    : Successful GET, PUT, PATCH, DELETE with body
  • 201 Created
    : Successful POST, return Location header
  • 202 Accepted
    : Async operation started
  • 204 No Content
    : Successful DELETE, no body
Client Error Codes:
  • 400 Bad Request
    : Invalid input, validation error
  • 401 Unauthorized
    : Missing or invalid authentication
  • 403 Forbidden
    : Authenticated but insufficient permissions
  • 404 Not Found
    : Resource doesn't exist
  • 409 Conflict
    : State conflict (duplicate, version mismatch)
  • 422 Unprocessable Entity
    : Semantic validation error
  • 429 Too Many Requests
    : Rate limit exceeded
Server Error Codes:
  • 500 Internal Server Error
    : Unexpected error
  • 502 Bad Gateway
    : Upstream service error
  • 503 Service Unavailable
    : Temporary outage
  • 504 Gateway Timeout
    : Upstream timeout
成功状态码:
  • 200 OK
    : 成功的GET、PUT、PATCH、DELETE请求,返回响应体
  • 201 Created
    : 成功的POST请求,返回Location请求头
  • 202 Accepted
    : 异步操作已启动
  • 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
    : 意外错误
  • 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
references/rest-patterns.md
for filtering, sorting, field selection, HATEOAS
偏移量分页(简单、易上手):
GET /users?limit=20&offset=40
✅ 适用场景: 小型数据集、管理后台 ❌ 避免场景: 大型数据集(偏移量过大会导致性能下降)、实时数据
游标分页(稳定、高效):
GET /users?limit=20&cursor=eyJpZCI6MTIzfQ
Response: { "data": [...], "next_cursor": "eyJpZCI6MTQzfQ" }
✅ 适用场景: 无限滚动、实时信息流、大型数据集 ❌ 避免场景: 随机访问、页码跳转
键集分页(性能优异):
GET /users?limit=20&after_id=123
✅ 适用场景: 有序数据、数据库索引友好 ❌ 避免场景: 复杂排序、多排序键
查看
references/rest-patterns.md
获取过滤、排序、字段选择、HATEOAS相关内容

GraphQL 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
references/graphql-patterns.md
for subscriptions, relay cursor connections, error handling
防止资源消耗过大的查询:
typescript
import { createComplexityLimitRule } from 'graphql-validation-complexity';

const server = new ApolloServer({
  schema,
  validationRules: [
    createComplexityLimitRule(1000, {
      onCost: (cost) => console.log('查询成本:', cost),
    }),
  ],
});
查看
references/graphql-patterns.md
获取订阅、Relay游标连接、错误处理相关内容

gRPC 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
references/grpc-patterns.md
for streaming, interceptors, metadata, health checks
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, "用户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.md
获取流处理、拦截器、元数据、健康检查相关内容

Versioning Strategies

版本控制策略

URI Versioning (Simple, Explicit)

URI版本控制(简单、明确)

Most common, easy to understand
GET /v1/users/123
GET /v2/users/123
Pros: 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+json
Pros: 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+json
Pros: 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
references/versioning-strategies.md
for detailed migration patterns
json
{
  "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.md
获取详细的迁移模式

Authentication & 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 requests
http
undefined
适用场景: 第三方访问、用户授权、令牌刷新
授权码流程(Web/移动端最安全):
1. 客户端重定向到/authorize
2. 用户完成身份验证并授予权限
3. 认证服务器重定向到回调地址并携带授权码
4. 客户端使用授权码交换访问令牌
5. 客户端使用访问令牌调用API
http
undefined

Request 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...
undefined
GET /v1/users/me Authorization: Bearer eyJhbGc...
undefined

JWT (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, RBAC
GET /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 size
Headers:
http
HTTP/1.1 200 OK
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 73
X-RateLimit-Reset: 1640995200
429 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: 1640995200
429响应:
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:
  1. First request: Process and store result with key
  2. Duplicate request (same key): Return stored result (200 or 201)
  3. 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"
}
服务端行为:
  1. 首次请求: 处理请求并存储结果与对应键
  2. 重复请求(相同键): 返回存储的结果(200或201状态码)
  3. 不同请求(相同键): 返回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
undefined

Get 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

undefined
undefined

Caching Strategies

缓存策略

HTTP Caching Headers

HTTP缓存头

http
undefined
http
undefined

Public, 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
undefined
Cache-Control: no-store, no-cache, must-revalidate
undefined

ETag Validation

ETag验证

http
undefined
http
undefined

Server 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
undefined
HTTP/1.1 304 Not Modified
undefined

Last-Modified

Last-Modified

http
GET /v1/users/123
Response:
  Last-Modified: Wed, 21 Oct 2025 07:28:00 GMT
http
GET /v1/users/123
Response:
  Last-Modified: Wed, 21 Oct 2025 07:28:00 GMT

Conditional 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

undefined
undefined

Webhooks

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: string
yaml
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: string

GraphQL 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文档最佳实践

  1. Interactive examples: Provide working code samples
  2. Authentication guide: Step-by-step auth setup
  3. Error catalog: Document all error codes with examples
  4. Rate limits: Clearly state limits and headers
  5. Changelog: Track breaking and non-breaking changes
  6. Migration guides: Version upgrade instructions
  7. SDKs: Provide client libraries for popular languages
  1. 交互式示例: 提供可运行的代码示例
  2. 身份验证指南: 分步的身份验证设置说明
  3. 错误目录: 记录所有错误码及示例
  4. 速率限制: 明确说明限制规则及响应头
  5. 变更日志: 跟踪破坏性与非破坏性变更
  6. 迁移指南: 版本升级说明
  7. 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 (
?expand=orders,profile
) or use GraphQL
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): 需多次请求获取关联数据 ✅ 解决方案: 支持关联数据扩展(
?expand=orders,profile
)或使用GraphQL
聊天式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

额外资源