mcpField
A
api-design

api-design

github.com/krzysztofsurdy开发3648 次安装6 天前更新
代码仓库

关于 api-design

面向 API 设计者和评审者的协议级设计参考,适用于各种语言与框架。内容涵盖 REST 与 GraphQL、错误处理、版本控制、分页和认证,并提供实践建议与质量检查清单。

主要功能

  1. 01REST 与 GraphQL 的选择指引
  2. 02资源、HTTP 与 Schema 设计原则
  3. 03错误格式与版本演进建议
  4. 04分页及认证授权指导
  5. 05反模式说明与质量检查清单

适用场景

  1. 01规划新 API 的协议与接口
  2. 02审查现有 API 的一致性
  3. 03制定错误响应与版本策略
  4. 04为团队建立 API 设计规范

安装

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

$npx skills add https://github.com/krzysztofsurdy/code-virtuoso --skill api-design

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

包含的文件

除 SKILL.md 外,安装时一起下载的参考资料和脚本。

references/
api-evolution.md
graphql-patterns.md
rest-patterns.md

详细介绍

API 设计

设计一致、可预测且易于演进的 API 的原则和模式。适用于任何语言或框架。重点是协议层面的设计决策,而非实现细节。

将 API 表面视为一种产品:使用者应该能够预测其行为、从错误中恢复,并在不阅读源代码的情况下完成集成。

适用场景

  • 从头设计新的公开或内部 API
  • 审查现有 API 的一致性和易用性
  • 为项目选择 REST 还是 GraphQL
  • 规划 API 版本管理或迁移策略
  • 定义各服务之间的错误响应契约
  • 为团队或组织制定 API 标准

REST 与 GraphQL

方面RESTGraphQL
最适用场景CRUD 操作繁多、以资源为中心的领域数据复杂且相互关联,客户端需求各异
数据获取每个端点的响应结构固定客户端指定所需的确切字段
过度获取很常见,端点会返回完整资源已消除,客户端只请求所需内容
获取不足很常见,需要多次往返请求已消除,单个查询即可跨越多个关系
缓存内置 HTTP 缓存(ETag、Cache-Control)需要自定义缓存(规范化存储、持久化查询)
文件上传原生支持 multipart需要变通方案(multipart 规范或单独的端点)
实时能力Webhook、SSE 或轮询规范内置订阅机制
工具链成熟度成熟:OpenAPI、Postman、HTTP 客户端持续发展中:Apollo、Relay、GraphiQL
学习曲线较低,基于现有 HTTP 知识较高:模式语言、解析器、查询优化
错误处理HTTP 状态码 + 响应正文已执行的操作通常返回 200,并在响应的 errors 数组中包含错误;请求错误可使用 4xx(GraphQL over HTTP 仍处于草案阶段)
版本管理URL 路径、标头或查询参数通过弃用和增量变更演进模式

选择 REST 的情况: 领域可映射为资源和 CRUD 操作,需要 HTTP 缓存,或客户端较简单(移动应用、第三方集成)。

选择 GraphQL 的情况: 客户端的数据需求各异,需要聚合多个后端服务,或希望前端与后端之间有强类型契约。

两者都适用。许多系统使用 REST 作为外部/公开 API,并使用 GraphQL 实现内部前后端通信。

REST 设计原则

REST API 将领域建模为资源,并使用 HTTP 语义对其进行操作。

核心规则:

  • 资源使用名词,而非动词:/orders,而不是 /getOrders
  • HTTP 方法充当动词:GET 用于读取,POST 用于创建,PUT 用于替换,PATCH 用于更新,DELETE 用于删除
  • URL 用于标识资源;查询参数用于筛选、排序或分页
  • 集合使用复数名词:/users、/users/{id}
  • 嵌套最多两层:/users/{id}/orders 可以;/users/{id}/orders/{id}/items/{id}/variants 不可以
  • 有意义地使用 HTTP 状态码。不要所有情况都返回 200
  • 通过 Accept 和 Content-Type 标头支持内容协商

HATEOAS(超媒体即应用状态引擎)通过在响应中包含链接来增强可发现性。它适用于公开 API,但对于内部服务往往有些过度:

{
  "id": 42,
  "status": "shipped",
  "_links": {
    "self": { "href": "/orders/42" },
    "cancel": { "href": "/orders/42/cancel", "method": "POST" },
    "customer": { "href": "/customers/7" }
  }
}

详细约定请参阅 REST 模式参考。

GraphQL 设计原则

GraphQL API 会公开强类型的架构,供客户端以声明式方式查询。

核心规则:

  • 先设计架构:在编写解析器之前定义类型系统
  • 类型表示领域概念;字段表示属性和关系
  • 查询读取数据,变更写入数据,订阅则流式传输数据
  • 使用类型系统实施约束(非空、枚举、输入类型)
  • 避免过深的嵌套架构,以免查询成本难以预测
  • 通过批处理(dataloader 模式)解决 N+1 问题
  • 限制查询深度和复杂度,防止滥用

架构优先示例:

type User {
  id: ID!
  name: String!
  email: String!
  orders(first: Int, after: String): OrderConnection!
}

type Order {
  id: ID!
  total: Float!
  status: OrderStatus!
  createdAt: DateTime!
}

enum OrderStatus {
  PENDING
  CONFIRMED
  SHIPPED
  DELIVERED
  CANCELLED
}

详细约定请参阅 GraphQL 模式参考。

错误处理

一致的错误格式是影响力最大的 API 设计决策之一。使用者应能以编程方式解析错误,而无需检查消息字符串。

RFC 9457 Problem Details 格式(推荐用于 REST;该规范取代了 RFC 7807):

使用 Content-Type: application/problem+json 发送。标准的五个成员(type、title、status、detail、instance)之外的成员均为扩展成员,例如下方的 errors。

{
  "type": "https://api.example.com/errors/insufficient-funds",
  "title": "Insufficient Funds",
  "status": 422,
  "detail": "Account balance is $10.00 but the transfer requires $50.00.",
  "instance": "/transfers/abc-123",
  "errors": [
    {
      "field": "amount",
      "code": "insufficient_funds",
      "message": "Transfer amount exceeds available balance"
    }
  ]
}

核心原则:

  • 使用机器可读的 type 或 code。客户端应根据代码分支处理,而不是根据消息
  • 包含便于调试的、人类可读的 detail
  • 为每种错误提供稳定且有文档说明的 type URI。只有在 HTTP 状态已说明全部信息时,才省略 type(或使用 about:blank)
  • 验证失败时返回字段级错误,以便客户端突出显示具体输入
  • 使用适当的 HTTP 状态码(REST)或结构化错误类型(GraphQL)
  • 生产环境中绝不暴露堆栈跟踪、内部路径或 SQL 查询
  • 包含关联 ID/请求 ID,以便跨服务追踪错误

GraphQL 错误约定:

{
  "data": { "createOrder": null },
  "errors": [
    {
      "message": "Insufficient funds",
      "extensions": {
        "code": "INSUFFICIENT_FUNDS",
        "field": "amount"
      }
    }
  ]
}

版本控制

版本控制策略决定如何发布变更,同时不破坏现有使用者。

策略机制优点缺点
URL 路径/v1/users明确,易于路由URL 冗余,难以弃用
Accept 请求头Accept: application/vnd.api+json;version=2URL 简洁,符合 HTTP 规范不够显眼,不便于随手测试
查询参数/users?version=2实现简单容易遗漏,缓存处理复杂

实用建议:

  • URL 路径版本控制最常见,也最容易让使用者理解
  • 仅在出现破坏性变更时才提升主版本号
  • 优先通过新增字段、新端点等方式对 API 进行增量演进,而不是创建新版本
  • 弃用某个版本时,应告知停止使用日期并提供迁移指南

详细策略请参阅 API 演进参考。

分页

每个列表端点都需要分页。游标分页与偏移分页会影响性能、一致性和客户端复杂度。

方式工作原理优点缺点
偏移量?offset=20&limit=10简单,支持“跳转到第 N 页”实时插入/删除时结果不一致,大型表上速度较慢
游标?after=abc123&limit=10适用于实时数据,规模扩大时仍有良好性能无法跳转到任意页面

最佳实践:

  • 设置最大页面大小(例如 100)和合理的默认值(例如 20)
  • 返回分页元数据:hasNextPage、hasPreviousPage、totalCount(若计算成本较低)
  • 如果计算 totalCount 的成本较高,则将其设为可选,或返回估算值
  • 对信息流、活动流以及任何频繁变化的数据使用游标
  • 对管理仪表板、报告以及浏览期间很少变化的数据集使用偏移量

游标分页响应示例:

{
  "data": [ ... ],
  "pagination": {
    "hasNextPage": true,
    "hasPreviousPage": false,
    "startCursor": "eyJpZCI6MX0=",
    "endCursor": "eyJpZCI6MTB9"
  }
}

身份验证和授权

身份验证用于验证身份(你是谁?)。授权用于验证权限(你能做什么?)。

机制使用场景说明
API keys服务到服务、简单集成易于实现;定期轮换;切勿在客户端代码中暴露
OAuth 2.0第三方访问、委托权限行业标准。对于 SPA/移动应用,使用 Authorization Code + PKCE
JWT(Bearer tokens)微服务的无状态身份验证仅包含必要的声明;设置较短的有效期;验证签名和声明
Session cookies基于浏览器的 Web 应用配合 CSRF 防护使用;设置 Secure、HttpOnly、SameSite 标志

最佳实践:

  • 始终使用 HTTPS,无一例外
  • 在 Authorization: Bearer <token> 标头中传递令牌,不要放在查询字符串中
  • 实施范围/权限,以进行精细的访问控制
  • 缺少或凭据无效时返回 401 Unauthorized,权限不足时返回 403 Forbidden
  • 对身份验证端点实施严格的速率限制,以防止暴力攻击
  • 支持令牌刷新流程,避免强制用户重新进行身份验证

常见反模式

反模式问题修复方法
过于频繁的 API 调用客户端需要 10 次以上请求才能渲染页面汇总相关数据;考虑使用 GraphQL 或组合端点
万能端点单个端点通过标志接受差异极大的负载拆分为语义明确、用途专一的端点
命名不一致混用 snake_case、camelCase、复数和单数选定一种约定,并在整个项目中统一执行
缺少分页列表端点返回无上限的结果始终对集合进行分页;设置最大页面大小
不进行版本控制的破坏性变更重命名或删除字段会在无提示的情况下破坏客户端使用版本控制,或仅通过新增内容进行演进
泄露内部信息在 URL 中暴露数据库列名、自增 ID映射为稳定的外部标识符(UUID、slug)
忽略幂等性重试 POST 会创建重复资源为非幂等操作支持幂等性密钥
所有情况都返回 200错误以 HTTP 200 和错误正文返回使用适当的 HTTP 状态码
不带时区的时间戳2024-01-15 14:30:00 含义不明确始终使用带时区的 ISO 8601 格式:2024-01-15T14:30:00Z

质量检查清单

发布或审查 API 之前,请检查以下各项:

  • 资源命名保持一致(使用复数名词,URL 中不使用动词)
  • HTTP 方法符合语义(GET 是安全的,PUT/DELETE 是幂等的)
  • 每个列表端点都已分页,并设有最大页面大小
  • 错误响应采用统一格式,并包含机器可读的代码
  • 需要进行身份验证,且使用 HTTPS
  • 已设置速率限制,并提供适当的标头(Retry-After,以及 RateLimit 字段或旧版 X-RateLimit-*)
  • 通过版本控制处理破坏性变更,或通过新增内容进行演进以避免此类变更
  • 每个端点都有请求/响应示例
  • 时间戳使用带时区的 ISO 8601 格式
  • ID 是稳定的外部标识符,而非内部自增 ID
  • 已为浏览器客户端配置 CORS(如适用)
  • 已为响应启用压缩(gzip/brotli)
  • API 文档由唯一可信来源生成(OpenAPI schema、GraphQL introspection)

评论

支持 Markdown