name: api-design-principles description: 掌握REST和GraphQL API设计原则,构建直观、可扩展、可维护的API,让开发者愉悦。在设计新API、审查API规范或建立API设计标准时使用。
API设计原则
掌握REST和GraphQL API设计原则,构建直观、可扩展、可维护的API,让开发者愉悦并经受时间考验。
何时使用此技能
- 设计新的REST或GraphQL API
- 重构现有API以提高可用性
- 为团队建立API设计标准
- 在实施前审查API规范
- 在API范式之间迁移(如从REST到GraphQL等)
- 创建开发者友好的API文档
- 针对特定用例优化API(如移动端、第三方集成)
核心概念
1. RESTful设计原则
面向资源的架构
- 资源是名词(如用户、订单、产品),而不是动词
- 使用HTTP方法进行操作(GET、POST、PUT、PATCH、DELETE)
- URL代表资源层次结构
- 一致的命名约定
HTTP方法语义:
GET:检索资源(幂等、安全)POST:创建新资源PUT:替换整个资源(幂等)PATCH:部分资源更新DELETE:删除资源(幂等)
2. GraphQL设计原则
模式优先开发
- 类型定义领域模型
- 查询用于读取数据
- 变更用于修改数据
- 订阅用于实时更新
查询结构:
- 客户端精确请求所需内容
- 单一端点,多种操作
- 强类型模式
- 内置自省
3. API版本化策略
URL版本化:
/api/v1/users
/api/v2/users
头部版本化:
Accept: application/vnd.api+json; version=1
查询参数版本化:
/api/users?version=1
REST API设计模式
模式1:资源集合设计
# 好:面向资源的端点
GET /api/users # 列出用户(带分页)
POST /api/users # 创建用户
GET /api/users/{id} # 获取特定用户
PUT /api/users/{id} # 替换用户
PATCH /api/users/{id} # 更新用户字段
DELETE /api/users/{id} # 删除用户
# 嵌套资源
GET /api/users/{id}/orders # 获取用户的订单
POST /api/users/{id}/orders # 为用户创建订单
# 差:面向操作的端点(避免)
POST /api/createUser
POST /api/getUserById
POST /api/deleteUser
模式2:分页和过滤
from fastapi import FastAPI, Query
from pydantic import BaseModel, Field
from typing import List, Optional
class PaginatedResponse(BaseModel):
items: List[dict]
total: int
page: int
page_size: int
pages: int
@property
def has_next(self) -> bool:
return self.page < self.pages
app = FastAPI()
@app.get("/api/users", response_model=PaginatedResponse)
async def list_users(
page: int = Query(1, ge=1),
page_size: int = Query(20, ge=1, le=100),
status: Optional[str] = Query(None),
search: Optional[str] = Query(None)
):
query = build_query(status=status, search=search)
total = await count_users(query)
offset = (page - 1) * page_size
users = await fetch_users(query, limit=page_size, offset=offset)
return PaginatedResponse(
items=users,
total=total,
page=page,
page_size=page_size,
pages=(total + page_size - 1) // page_size
)
模式3:错误处理和状态码
from fastapi import HTTPException, status
STATUS_CODES = {
"success": 200,
"created": 201,
"no_content": 204,
"bad_request": 400,
"unauthorized": 401,
"forbidden": 403,
"not_found": 404,
"conflict": 409,
"unprocessable": 422,
"internal_error": 500
}
def raise_not_found(resource: str, id: str):
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail={
"error": "NotFound",
"message": f"{resource} not found",
"details": {"id": id}
}
)
模式4:HATEOAS(超媒体作为应用状态引擎)
class UserResponse(BaseModel):
id: str
name: str
email: str
_links: dict
@classmethod
def from_user(cls, user: User, base_url: str):
return cls(
id=user.id,
name=user.name,
email=user.email,
_links={
"self": {"href": f"{base_url}/api/users/{user.id}"},
"orders": {"href": f"{base_url}/api/users/{user.id}/orders"},
"update": {"href": f"{base_url}/api/users/{user.id}", "method": "PATCH"},
"delete": {"href": f"{base_url}/api/users/{user.id}", "method": "DELETE"}
}
)
GraphQL设计模式
模式1:模式设计
type User {
id: ID!
email: String!
name: String!
orders(first: Int = 20, after: String): OrderConnection!
}
type OrderConnection {
edges: [OrderEdge!]!
pageInfo: PageInfo!
totalCount: Int!
}
type PageInfo {
hasNextPage: Boolean!
hasPreviousPage: Boolean!
startCursor: String
endCursor: String
}
type Mutation {
createUser(input: CreateUserInput!): CreateUserPayload!
}
input CreateUserInput {
email: String!
name: String!
password: String!
}
type CreateUserPayload {
user: User
errors: [Error!]
}
模式2:数据加载器(预防N+1问题)
from aiodataloader import DataLoader
class UserLoader(DataLoader):
async def batch_load_fn(self, user_ids: List[str]) -> List[Optional[dict]]:
users = await fetch_users_by_ids(user_ids)
user_map = {user["id"]: user for user in users}
return [user_map.get(user_id) for user_id in user_ids]
最佳实践
REST API
- 一致命名:对集合使用复数名词
- 无状态:每个请求包含所有必要信息
- 正确使用HTTP状态码:2xx成功,4xx客户端错误,5xx服务器错误
- 版本化您的API:从第一天起就规划破坏性变更
- 分页:始终对大集合进行分页
- 速率限制:通过速率限制保护您的API
- 文档:使用OpenAPI/Swagger进行交互式文档
GraphQL API
- 模式优先:在编写解析器前设计模式
- 避免N+1:使用数据加载器进行高效数据获取
- 输入验证:在模式和解析器层面进行验证
- 错误处理:在变更负载中返回结构化错误
- 分页:使用基于游标的分页(Relay规范)
- 弃用:使用
@deprecated指令进行渐进式迁移 - 监控:跟踪查询复杂性和执行时间
常见陷阱
- 过度获取/获取不足(REST):GraphQL中已解决,但需要数据加载器
- 破坏性变更:版本化API或使用弃用策略
- 不一致的错误格式:标准化错误响应
- 缺少速率限制:没有限制的API易受滥用
- 文档质量差:未文档化的API让开发者沮丧
- 忽略HTTP语义:用POST进行幂等操作违背期望
- 紧耦合:API结构不应镜像数据库模式