mcpField
A
api-design-patterns

api-design-patterns

github.com/hieutrtr开发870 次安装39 周前更新
代码仓库

关于 api-design-patterns

api-design-patterns 是用于实现前设计和审查 API 契约的 skill,涵盖端点命名、架构、分页、错误、版本管理和文档,并将设计写入 api-design.md。

主要功能

  1. 01规定 URL 与 HTTP 方法规范
  2. 02统一请求和响应架构
  3. 03涵盖分页、筛选与排序
  4. 04定义错误格式和版本管理
  5. 05指导 OpenAPI 文档编写

适用场景

  1. 01设计新的 API 契约
  2. 02审查端点一致性
  3. 03规划分页与筛选
  4. 04定义 API 错误与版本策略

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/hieutrtr/ai1-skills --skill api-design-patterns

安装后在 Claude Code 中输入/api-design-patterns确认已加载。

附带文件references/endpoint-catalog-template.mdreferences/pydantic-schema-examples.md

详细介绍

API 设计模式

适用场景

在以下情况下启用此技能:

  • 设计新的 API 端点或修改现有端点契约
  • 为某项功能定义请求/响应架构
  • 统一各端点的分页、筛选或排序方式
  • 设计一致的错误响应格式
  • 规划 API 版本管理或弃用策略
  • 在实施前检查 API 契约的一致性
  • 编写端点规范文档,以协调前端和后端工作

输入: 如果存在 plan.md 或 architecture.md,请阅读以了解功能范围和架构决策。否则,直接根据用户请求开展工作。

输出: 将 API 设计写入 api-design.md。告知用户:“API 设计已写入 api-design.md。运行 /task-decomposition 创建实施任务,或运行 /python-backend-expert 进行实现。”

请勿将此技能用于:

  • 编写实现代码(使用 python-backend-expert)
  • 制定系统级架构决策(使用 system-architecture)
  • 为端点编写测试(使用 pytest-patterns)
  • 实现前端数据获取(使用 react-frontend-expert)

说明

URL 命名约定
资源命名规则
  1. 集合使用复数名词:/users、/orders、/products
  2. 多词资源使用 kebab-case:/order-items、/user-profiles
  3. 按 ID 标识的资源使用单数形式:/users/{user_id}、/orders/{order_id}
  4. 最多嵌套 2 层:/users/{user_id}/orders(不要使用 /users/{user_id}/orders/{order_id}/items/{item_id})
  5. URL 中不使用动词:使用 HTTP 方法(POST /orders,而不是 /orders/create)
  6. 使用查询参数进行筛选、排序和分页:/users?role=admin&sort=-created_at
URL 结构模板
/{version}/{resource}                    → Collection (list, create)
/{version}/{resource}/{id}               → Single resource (get, update, delete)
/{version}/{resource}/{id}/{sub-resource} → Nested collection
/{version}/{resource}/actions/{action}   → Non-CRUD operations (rarely needed)
命名示例
良好示例不良示例原因
GET /v1/usersGET /v1/getUsers不使用动词——HTTP 方法已表明操作
POST /v1/usersPOST /v1/user/create对集合执行 POST = 创建
GET /v1/order-itemsGET /v1/orderItems使用 kebab-case,而不是 camelCase
GET /v1/users/{id}/ordersGET /v1/users/{id}/orders/{oid}/items最多嵌套 2 层
POST /v1/orders/{id}/actions/cancelPOST /v1/cancelOrder/{id}非 CRUD 操作使用动作子资源
HTTP 方法语义
方法用途请求正文成功状态幂等
GET获取资源无200 OK是
POST创建新资源必需201 Created否
PUT完整替换必需(完整内容)200 OK是
PATCH部分更新必需(部分内容)200 OK否*
DELETE删除资源无204 No Content是

*PATCH 本身并非天然幂等,但可以通过适当实现使其幂等。

创建操作的响应头:

  • 返回 201 的 POST 应包含 Location 响应头,其中提供所创建资源的 URL

条件请求:

  • 对频繁访问资源的 GET 端点,支持使用 If-None-Match / ETag 进行缓存
Schema 命名约定(Pydantic v2)

所有 Pydantic schema 均遵循一致的命名模式:

模式用途字段
{Resource}CreatePOST 请求体可写字段,不包含 id 和时间戳
{Resource}UpdatePUT 请求体所有可写字段均为必填
{Resource}PatchPATCH 请求体所有字段均为 Optional
{Resource}Response单个资源响应所有字段,包括 id 和时间戳
{Resource}ListResponse分页列表响应items + 分页元数据
{Resource}Filter查询参数可选筛选字段

Schema 设计规则:

  • Response schema 中不得暴露内部字段(hashed_password、internal_notes)
  • Response schema 中始终包含 id 和时间戳(created_at、updated_at)
  • 使用 model_validate(orm_instance) 将 ORM 模型转换为响应 schema
  • PATCH 操作使用 model_dump(exclude_unset=True),以区分“未提供”和“设为 null”
  • 具体示例请参阅 references/pydantic-schema-examples.md
分页
基于游标的分页(默认)

所有列表端点均使用基于游标的分页。对于大型数据集,这种方式的性能优于基于偏移量的分页,也能避免“窗口移动”问题。

请求参数:

GET /v1/users?cursor=eyJpZCI6MTAwfQ&limit=20
参数类型默认值说明
cursorstr | NoneNone来自先前响应的不透明游标
limitint20每页项目数(最多 100)

响应格式:

{
  "items": [...],
  "next_cursor": "eyJpZCI6MTIwfQ",
  "has_more": true
}

游标实现:

  • 将最后一项的排序键(通常为 id)编码为 base64 字符串
  • 游标对客户端是不透明的——客户端不得解析或构造游标
  • 使用 WHERE id > :last_id ORDER BY id ASC LIMIT :limit + 1——多获取一项,以确定 has_more
基于偏移量的分页(按需使用)

仅当客户端需要跳转到任意页面时(例如,管理后台表格)才使用基于偏移量的分页。

{
  "items": [...],
  "total": 150,
  "page": 2,
  "page_size": 20,
  "total_pages": 8
}
筛选与排序
筛选

使用字段名作为查询参数:

GET /v1/users?role=admin&is_active=true&created_after=2024-01-01

筛选约定:

  • 精确匹配:?field=value
  • 范围:?field_min=10&field_max=100 或 ?created_after=...&created_before=...
  • 搜索:?q=search+term(用于跨多个字段进行全文搜索)
  • 多个值:?status=active&status=pending(OR 语义)
排序

使用 sort 查询参数,并指定字段名和方向前缀:

GET /v1/users?sort=-created_at        → descending by created_at
GET /v1/users?sort=name               → ascending by name
GET /v1/users?sort=-created_at,name   → multi-field sort

**约定:**前缀 - 表示降序;无前缀表示升序。

错误响应格式

所有 API 错误均遵循一致的格式:

{
  "detail": "Human-readable error message",
  "code": "MACHINE_READABLE_CODE",
  "field_errors": [
    {
      "field": "email",
      "message": "Invalid email format",
      "code": "INVALID_FORMAT"
    }
  ]
}
标准错误代码与状态映射
HTTP 状态使用场景示例 code
400请求格式错误BAD_REQUEST
401缺少身份验证信息或身份验证无效UNAUTHORIZED
403已通过身份验证,但未获授权FORBIDDEN
404未找到资源NOT_FOUND
409冲突(重复、版本不匹配)CONFLICT
422验证错误(Pydantic)VALIDATION_ERROR
429超出速率限制RATE_LIMITED
500意外的服务端错误INTERNAL_ERROR

错误 schema(Pydantic v2):

class FieldError(BaseModel):
    field: str
    message: str
    code: str

class ErrorResponse(BaseModel):
    detail: str
    code: str
    field_errors: list[FieldError] = []
API 版本控制
策略:URL 前缀版本控制
/v1/users    → Version 1
/v2/users    → Version 2

版本控制规则:

  1. 所有新 API 都从 /v1/ 开始
  2. 仅在有破坏性变更时递增主版本号
  3. 非破坏性变更(新增可选字段、新增端点)不需要新版本
  4. 同时最多支持 2 个活跃版本

需要新版本的破坏性变更:

  • 从响应中移除字段
  • 更改字段类型
  • 将可选请求字段设为必填
  • 更改现有端点的 URL 结构
  • 更改错误响应格式

弃用流程:

  1. 为旧版本添加 Deprecation 标头:Deprecation: true
  2. 添加带有退役日期的 Sunset 标头:Sunset: Sat, 01 Mar 2026 00:00:00 GMT
  3. 添加指向新版本的 Link 标头:Link: </v2/users>; rel="successor-version"
  4. 记录已弃用端点的使用情况以进行监控
  5. 在日落日期之后移除旧版本
OpenAPI 文档

FastAPI 会自动生成 OpenAPI 架构。通过以下方式完善文档:

@router.get(
    "/users/{user_id}",
    response_model=UserResponse,
    summary="Get user by ID",
    description="Retrieve a single user's details by their unique identifier.",
    responses={
        404: {"model": ErrorResponse, "description": "User not found"},
    },
    tags=["Users"],
)
async def get_user(user_id: int) -> UserResponse:
    ...

文档约定:

  • 每个端点都有一个 summary(简短)和可选的 description(详细)
  • 使用对应的架构记录所有非 200 响应
  • 按与资源名称匹配的 tags 对端点分组
  • 使用 response_model 自动记录响应架构

示例

设计 Products API 契约

目标: 为带搜索和分页功能的 /v1/products CRUD 端点设计契约。

端点:

方法路径描述请求响应状态
GET/v1/products列出产品查询:cursor、limit、q、category、sortProductListResponse200
POST/v1/products创建产品请求体:ProductCreateProductResponse201
GET/v1/products/{id}获取产品—ProductResponse200
PATCH/v1/products/{id}更新产品请求体:ProductPatchProductResponse200
DELETE/v1/products/{id}删除产品——204

架构:

class ProductCreate(BaseModel):
    name: str = Field(min_length=1, max_length=200)
    description: str | None = None
    price_cents: int = Field(gt=0)
    category: str
    sku: str = Field(pattern=r"^[A-Z0-9-]+$")

class ProductPatch(BaseModel):
    name: str | None = None
    description: str | None = None
    price_cents: int | None = Field(default=None, gt=0)
    category: str | None = None

class ProductResponse(BaseModel):
    id: int
    name: str
    description: str | None
    price_cents: int
    category: str
    sku: str
    created_at: datetime
    updated_at: datetime

class ProductListResponse(BaseModel):
    items: list[ProductResponse]
    next_cursor: str | None
    has_more: bool

搜索和筛选:

GET /v1/products?q=laptop&category=electronics&sort=-price_cents&limit=20

完整的文档模板请参见 references/endpoint-catalog-template.md。 更多架构示例请参见 references/pydantic-schema-examples.md。

边缘情况

批量操作

对于一次处理多个资源的操作:

POST /v1/users/bulk

请求:

{
  "items": [
    {"email": "[email protected]", "name": "Alice"},
    {"email": "[email protected]", "name": "Bob"}
  ]
}

响应(部分成功 — 状态码 207):

{
  "results": [
    {"index": 0, "status": "created", "data": {...}},
    {"index": 1, "status": "error", "error": {"detail": "Email already exists", "code": "CONFLICT"}}
  ],
  "succeeded": 1,
  "failed": 1
}

当各个项目可以独立成功或失败时,使用 HTTP 207 Multi-Status。

文件上传端点

文件上传使用 multipart/form-data,而非 JSON:

@router.post("/v1/files", response_model=FileResponse, status_code=201)
async def upload_file(
    file: UploadFile,
    description: str = Form(default=""),
) -> FileResponse:
    ...

处理前验证文件大小和 MIME 类型。对于超大文件,返回 413 Payload Too Large。

长时间运行的操作

对于无法在正常请求超时时间内完成的操作:

  1. 返回带有状态 URL 的 202 Accepted:

    {"status_url": "/v1/jobs/abc123", "estimated_completion": "2024-01-15T10:30:00Z"}
    
  2. 客户端轮询状态 URL:

    GET /v1/jobs/abc123 → {"status": "processing", "progress": 0.65}
    GET /v1/jobs/abc123 → {"status": "completed", "result_url": "/v1/reports/xyz"}
    
子资源设计

当资源在逻辑上属于父资源,但嵌套层级会超过 2 层时,使用带筛选条件的顶层资源:

# Instead of: GET /v1/users/{id}/orders/{oid}/items
# Use:        GET /v1/order-items?order_id=123

这样既能保持 URL 简洁,又能通过筛选条件维护关联关系。

输出文件

将 API 设计写入项目根目录下的 api-design.md:

# API Design: [Feature Name]

## Endpoints

| Method | URL | Description | Auth |
|--------|-----|-------------|------|
| GET | /v1/users | List users | Required |
| POST | /v1/users | Create user | Required |

## Request/Response Schemas

### UserCreate
| Field | Type | Required | Validation |
|-------|------|----------|------------|
| email | string | Yes | Valid email |
| name | string | Yes | 1-100 chars |

### UserResponse
| Field | Type | Description |
|-------|------|-------------|
| id | uuid | User ID |
| email | string | User email |

## Error Codes

| Code | HTTP Status | Description |
|------|-------------|-------------|
| USER_NOT_FOUND | 404 | User does not exist |
| EMAIL_EXISTS | 409 | Email already registered |

## Next Steps
- Run `/task-decomposition` to create implementation tasks
- Run `/python-backend-expert` to implement endpoints

评论

支持 Markdown