api-design

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

API Design

API设计

Core Principles

核心原则

  • Contract-First — Define API spec before implementation
  • OpenAPI 3.2 — Use OpenAPI for REST API documentation
  • URL Versioning — Version in path
    /v1/
    , with Sunset headers
  • Idempotency — PUT/DELETE must be idempotent, POST uses Idempotency-Key
  • Cursor Pagination — Avoid offset-based pagination
  • RFC 7807 Errors — Standard Problem Details format
  • No backwards compatibility — Delete, don't deprecate

  • 契约优先 — 在实现前定义API规范
  • OpenAPI 3.2 — 使用OpenAPI进行REST API文档编写
  • URL版本控制 — 在路径中加入版本号
    /v1/
    ,搭配Sunset响应头
  • 幂等性 — PUT/DELETE请求必须具备幂等性,POST请求使用Idempotency-Key
  • 游标分页 — 避免基于偏移量的分页
  • RFC 7807错误格式 — 使用标准的问题详情格式
  • 不保留向后兼容性 — 直接删除,而非标记为废弃

Quick Reference

快速参考

When to Use What

选型指南

ScenarioChoiceReason
Public API / MVPRESTSimple, universal, easy debugging
Frontend-driven / MobileGraphQLFetch exactly what you need
Microservices internalgRPCHigh performance, strong typing
Real-time datagRPC / GraphQL SubscriptionsBidirectional streaming

场景选择原因
公开API / 最小可行产品(MVP)REST简单通用、易于调试
前端驱动 / 移动端GraphQL按需获取所需数据
微服务内部通信gRPC高性能、强类型约束
实时数据传输gRPC / GraphQL Subscriptions双向流式传输

REST API Design

REST API设计

Resource Naming

资源命名

undefined
undefined

Good

推荐写法

GET /users # List users GET /users/123 # Get user POST /users # Create user PUT /users/123 # Replace user PATCH /users/123 # Update user DELETE /users/123 # Delete user
GET /users # 列出用户 GET /users/123 # 获取单个用户 POST /users # 创建用户 PUT /users/123 # 替换用户资源 PATCH /users/123 # 更新用户资源 DELETE /users/123 # 删除用户

Nested resources

嵌套资源

GET /users/123/orders # User's orders
GET /users/123/orders # 获取用户的订单

Actions (when CRUD doesn't fit)

自定义操作(当CRUD不适用时)

POST /users/123/activate # Action on resource
POST /users/123/activate # 对资源执行激活操作

Query parameters for filtering

查询参数用于过滤

GET /users?status=active&role=admin&limit=20
undefined
GET /users?status=active&role=admin&limit=20
undefined

HTTP Methods

HTTP方法

MethodPurposeIdempotentSafe
GETReadYesYes
POSTCreateNoNo
PUTReplaceYesNo
PATCHUpdateNoNo
DELETERemoveYesNo
方法用途幂等性安全性
GET读取资源
POST创建资源
PUT替换资源
PATCH更新资源
DELETE删除资源

Status Codes

状态码

undefined
undefined

Success

成功响应

200 OK - Successful GET/PUT/PATCH 201 Created - Successful POST (include Location header) 204 No Content - Successful DELETE
200 OK - 成功的GET/PUT/PATCH请求 201 Created - 成功的POST请求(需包含Location响应头) 204 No Content - 成功的DELETE请求

Client Errors

客户端错误

400 Bad Request - Malformed request syntax 401 Unauthorized - Missing/invalid authentication 403 Forbidden - Authenticated but not authorized 404 Not Found - Resource doesn't exist 409 Conflict - Duplicate/conflict (e.g., unique constraint) 422 Unprocessable - Validation failed 429 Too Many - Rate limited
400 Bad Request - 请求格式错误 401 Unauthorized - 缺少或无效的身份验证信息 403 Forbidden - 已通过身份验证,但无权限访问 404 Not Found - 资源不存在 409 Conflict - 资源冲突(如违反唯一约束) 422 Unprocessable - 请求参数验证失败 429 Too Many - 请求频率超限

Server Errors

服务端错误

500 Internal Error - Unexpected server error 503 Unavailable - Service temporarily down
undefined
500 Internal Error - 意外的服务端错误 503 Unavailable - 服务暂时不可用
undefined

Error Response (RFC 7807)

错误响应(RFC 7807)

json
{
  "type": "https://api.example.com/errors/validation",
  "title": "Validation Error",
  "status": 422,
  "detail": "The request contains invalid parameters",
  "instance": "/users/123",
  "errors": [
    { "field": "email", "message": "Invalid email format" },
    { "field": "age", "message": "Must be positive integer" }
  ]
}
json
{
  "type": "https://api.example.com/errors/validation",
  "title": "Validation Error",
  "status": 422,
  "detail": "The request contains invalid parameters",
  "instance": "/users/123",
  "errors": [
    { "field": "email", "message": "Invalid email format" },
    { "field": "age", "message": "Must be positive integer" }
  ]
}

Pagination (Cursor-Based)

游标分页

json
// Request
GET /users?limit=20&cursor=eyJpZCI6MTAwfQ

// Response
{
  "data": [...],
  "pagination": {
    "next_cursor": "eyJpZCI6MTIwfQ",
    "prev_cursor": "eyJpZCI6ODB9",
    "has_next": true,
    "has_prev": true,
    "limit": 20
  }
}
json
// 请求示例
GET /users?limit=20&cursor=eyJpZCI6MTAwfQ

// 响应示例
{
  "data": [...],
  "pagination": {
    "next_cursor": "eyJpZCI6MTIwfQ",
    "prev_cursor": "eyJpZCI6ODB9",
    "has_next": true,
    "has_prev": true,
    "limit": 20
  }
}

Versioning

版本管理

undefined
undefined

URL versioning (recommended)

URL版本控制(推荐方案)

GET /v1/users GET /v2/users
GET /v1/users GET /v2/users

Deprecation headers

废弃通知头

Sunset: Sat, 31 Dec 2025 23:59:59 GMT Deprecation: true Link: </v2/users>; rel="successor-version"
undefined
Sunset: Sat, 31 Dec 2025 23:59:59 GMT Deprecation: true Link: </v2/users>; rel="successor-version"
undefined

Idempotency

幂等性实现

undefined
undefined

For non-idempotent operations (POST)

针对非幂等操作(如POST)

POST /orders Idempotency-Key: 550e8400-e29b-41d4-a716-446655440000
POST /orders Idempotency-Key: 550e8400-e29b-41d4-a716-446655440000

Server stores result and returns same response for duplicate key

服务端存储请求结果,针对相同的Key返回一致的响应


---

---

GraphQL Design

GraphQL设计

Schema Principles

架构原则

  • Domain-driven — Schema reflects business domain, not database
  • Descriptive names — Clear field/type names for monitoring
  • Limit nesting — Deep nesting hurts performance
  • Use @key — Mark entity identifiers for Federation
  • 领域驱动 — 架构反映业务领域,而非数据库结构
  • 命名清晰 — 字段/类型名称具有描述性,便于监控
  • 限制嵌套层级 — 过深的嵌套会影响性能
  • 使用@key — 标记实体标识符以支持Federation

Type Definitions

类型定义

graphql
type Query {
  user(id: ID!): User
  users(first: Int, after: String, filter: UserFilter): UserConnection!
}

type Mutation {
  createUser(input: CreateUserInput!): CreateUserPayload!
  updateUser(id: ID!, input: UpdateUserInput!): UpdateUserPayload!
}

type User @key(fields: "id") {
  id: ID!
  email: String!
  name: String!
  orders(first: Int, after: String): OrderConnection!
  createdAt: DateTime!
}
graphql
type Query {
  user(id: ID!): User
  users(first: Int, after: String, filter: UserFilter): UserConnection!
}

type Mutation {
  createUser(input: CreateUserInput!): CreateUserPayload!
  updateUser(id: ID!, input: UpdateUserInput!): UpdateUserPayload!
}

type User @key(fields: "id") {
  id: ID!
  email: String!
  name: String!
  orders(first: Int, after: String): OrderConnection!
  createdAt: DateTime!
}

Relay-style pagination

Relay风格分页

type UserConnection { edges: [UserEdge!]! pageInfo: PageInfo! totalCount: Int! }
type UserEdge { node: User! cursor: String! }
type PageInfo { hasNextPage: Boolean! hasPreviousPage: Boolean! startCursor: String endCursor: String }
undefined
type UserConnection { edges: [UserEdge!]! pageInfo: PageInfo! totalCount: Int! }
type UserEdge { node: User! cursor: String! }
type PageInfo { hasNextPage: Boolean! hasPreviousPage: Boolean! startCursor: String endCursor: String }
undefined

Error Handling

错误处理

graphql
type Mutation {
  createUser(input: CreateUserInput!): CreateUserPayload!
}
graphql
type Mutation {
  createUser(input: CreateUserInput!): CreateUserPayload!
}

Union for typed errors

使用Union类型处理强类型错误

union CreateUserPayload = User | ValidationError | ConflictError
type ValidationError { message: String! field: String code: String! }
type ConflictError { message: String! existingId: ID! }
undefined
union CreateUserPayload = User | ValidationError | ConflictError
type ValidationError { message: String! field: String code: String! }
type ConflictError { message: String! existingId: ID! }
undefined

N+1 Prevention

N+1问题解决

typescript
// Use DataLoader for batching
const userLoader = new DataLoader(async (ids: string[]) => {
  const users = await db.user.findMany({
    where: { id: { in: ids } }
  });
  return ids.map(id => users.find(u => u.id === id));
});

// Resolver
const resolvers = {
  Order: {
    user: (order) => userLoader.load(order.userId),
  },
};

typescript
// 使用DataLoader进行批量查询
const userLoader = new DataLoader(async (ids: string[]) => {
  const users = await db.user.findMany({
    where: { id: { in: ids } }
  });
  return ids.map(id => users.find(u => u.id === id));
});

// 解析器
const resolvers = {
  Order: {
    user: (order) => userLoader.load(order.userId),
  },
};

gRPC Design

gRPC设计

Proto Definition

Proto定义

protobuf
syntax = "proto3";

package api.v1;

import "google/protobuf/timestamp.proto";
import "google/protobuf/empty.proto";

service UserService {
  // Unary
  rpc GetUser(GetUserRequest) returns (User);
  rpc CreateUser(CreateUserRequest) returns (User);

  // Server streaming
  rpc ListUsers(ListUsersRequest) returns (stream User);

  // Client streaming
  rpc BatchCreateUsers(stream CreateUserRequest) returns (BatchCreateResponse);

  // Bidirectional streaming
  rpc SyncUsers(stream UserUpdate) returns (stream UserUpdate);
}

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;
  UserFilter filter = 3;
}

message UserFilter {
  optional string status = 1;
  optional string role = 2;
}
protobuf
syntax = "proto3";

package api.v1;

import "google/protobuf/timestamp.proto";
import "google/protobuf/empty.proto";

service UserService {
  // 一元调用
  rpc GetUser(GetUserRequest) returns (User);
  rpc CreateUser(CreateUserRequest) returns (User);

  // 服务端流式调用
  rpc ListUsers(ListUsersRequest) returns (stream User);

  // 客户端流式调用
  rpc BatchCreateUsers(stream CreateUserRequest) returns (BatchCreateResponse);

  // 双向流式调用
  rpc SyncUsers(stream UserUpdate) returns (stream UserUpdate);
}

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;
  UserFilter filter = 3;
}

message UserFilter {
  optional string status = 1;
  optional string role = 2;
}

Error Handling

错误处理

protobuf
// Use Google's richer error model
import "google/rpc/status.proto";
import "google/rpc/error_details.proto";

// For streaming: embed errors in response
message StreamResponse {
  oneof result {
    User user = 1;
    StreamError error = 2;
  }
}

message StreamError {
  string code = 1;
  string message = 2;
  map<string, string> details = 3;
}
protobuf
// 使用Google的增强错误模型
import "google/rpc/status.proto";
import "google/rpc/error_details.proto";

// 流式调用:在响应中嵌入错误信息
message StreamResponse {
  oneof result {
    User user = 1;
    StreamError error = 2;
  }
}

message StreamError {
  string code = 1;
  string message = 2;
  map<string, string> details = 3;
}

Deadlines & Retries

超时与重试

typescript
// Always set deadlines
const deadline = new Date();
deadline.setSeconds(deadline.getSeconds() + 5);

const user = await client.getUser(
  { id: '123' },
  { deadline }
);

// Configure retry policy
const retryPolicy = {
  maxAttempts: 3,
  initialBackoff: '0.1s',
  maxBackoff: '1s',
  backoffMultiplier: 2,
  retryableStatusCodes: ['UNAVAILABLE', 'DEADLINE_EXCEEDED'],
};

typescript
// 始终设置超时时间
const deadline = new Date();
deadline.setSeconds(deadline.getSeconds() + 5);

const user = await client.getUser(
  { id: '123' },
  { deadline }
);

// 配置重试策略
const retryPolicy = {
  maxAttempts: 3,
  initialBackoff: '0.1s',
  maxBackoff: '1s',
  backoffMultiplier: 2,
  retryableStatusCodes: ['UNAVAILABLE', 'DEADLINE_EXCEEDED'],
};

Rate Limiting

请求频率限制

Headers

响应头

X-RateLimit-Limit: 100
X-RateLimit-Remaining: 95
X-RateLimit-Reset: 1640995200
Retry-After: 60
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 95
X-RateLimit-Reset: 1640995200
Retry-After: 60

Response (429)

429响应示例

json
{
  "type": "https://api.example.com/errors/rate-limited",
  "title": "Rate Limit Exceeded",
  "status": 429,
  "detail": "You have exceeded the rate limit of 100 requests per minute",
  "retryAfter": 60
}

json
{
  "type": "https://api.example.com/errors/rate-limited",
  "title": "Rate Limit Exceeded",
  "status": 429,
  "detail": "You have exceeded the rate limit of 100 requests per minute",
  "retryAfter": 60
}

Checklist

检查清单

markdown
undefined
markdown
undefined

Design

设计阶段

  • API spec defined before implementation
  • Resources use plural nouns
  • Correct HTTP methods/status codes
  • RFC 7807 error format
  • 先定义API规范再进行实现
  • 资源使用复数名词
  • 使用正确的HTTP方法和状态码
  • 采用RFC 7807错误格式

Features

功能特性

  • Cursor-based pagination
  • Rate limiting with headers
  • Idempotency keys for POST
  • API versioning strategy
  • 实现游标分页
  • 搭配响应头的请求频率限制
  • 为POST请求添加幂等性Key
  • 制定API版本管理策略

Documentation

文档

  • OpenAPI/GraphQL schema published
  • Examples for all endpoints
  • Error codes documented
  • 发布OpenAPI/GraphQL架构文档
  • 为所有端点提供示例
  • 记录所有错误码

Operations

运维

  • Request/response logging
  • Latency and error rate metrics
  • Deprecation notices for old versions

---
  • 记录请求/响应日志
  • 监控延迟和错误率指标
  • 为旧版本添加废弃通知

---

See Also

扩展参考

  • reference/rest.md — REST deep dive
  • reference/graphql.md — GraphQL patterns
  • reference/grpc.md — gRPC patterns
  • reference/comparison.md — Selection guide
  • reference/rest.md — REST深度解析
  • reference/graphql.md — GraphQL设计模式
  • reference/grpc.md — gRPC设计模式
  • reference/comparison.md — API选型指南