关于 awesome-api-design
一项用于设计和审查 HTTP API 契约的技能,适合创建或演进 API 的团队。内容涵盖资源建模、兼容性、分页、重试、并发控制和机器可读规范。
主要功能
- 01面向资源的 URL 与方法设计
- 02版本管理与弃用规则
- 03游标分页与白名单筛选
- 04幂等与并发控制
- 05以 OpenAPI 或 Schema 作为契约依据
适用场景
- 01设计新的 HTTP API
- 02审查 API 或 OpenAPI 规范
- 03新增符合现有 API 约定的端点
- 04检查已发布契约变更的兼容性
安装
复制下面的命令,在终端中运行。
$
npx skills add https:/ / github.com/ khasky/ awesome-agent-skills --skill awesome-api-design安装后在 Claude Code 中输入/awesome-api-design确认已加载。
附带文件evals/eval_queries.json
详细介绍
API 设计
设计 HTTP API,让使用者多年后仍能基于它构建,而不会遭遇意外的不兼容变更。契约就是产品:URL、方法、负载、错误和演进规则都要在此明确决定——凡是未明确规定的内容,都会变成首个依赖它的客户端所依赖的意外契约。
何时启用
- “为 X 设计 API”“审查此 API 设计/OpenAPI 规范”“应该如何进行版本管理/分页/处理重试”。
- 设计文档需要把 API 部分具体化(awesome-design-doc 会在此移交)。
- 现有 API 要新增端点,并且必须符合其约定。
不要用它来定义错误封装的字段或重试分类(这部分契约由 awesome-error-standards 负责),也不要用它来审计已实现处理程序中的漏洞(awesome-security-audit)。
工作流程
- 梳理现有约定和项目自己的用词——现有 API 的大小写、ID 格式、分页样式和封装格式,优先于此处的任何指南:同一 API 内的一致性胜过全局最佳实践。只有新 API 才从以下默认值开始。在命名任何资源之前,先阅读已记录的决策(现有 ADR)和项目词汇表(
CONTEXT.md、领域文档,或代码和测试中已使用的术语):路径是公开且长期存在的名称;若它改名了代码库中已有的概念,每位读者都得进行一次术语转换。 - 对资源建模,而非对过程建模——使用具有身份和生命周期的名词(
/orders/{id}),并将操作作为其状态转换(无法避免纯动词时用POST /orders/{id}/cancel,绝不要用/doCancelOrder)。嵌套最多一层;更深的层级应改用查询筛选条件(/comments?post_id=…),因为每一层嵌套都会把所有权假设硬编码到每个客户端 URL 中。 - 在 v1 发布前决定演进规则——增量变更(新增可选字段、新增端点)原地发布;任何破坏性变更(删除/重命名/更改字段类型、收紧验证规则)都必须创建新版本。将此分类规则与 API 一同发布,让使用者知道哪些内容可以安全忽略。通过分层演进——重新设计的抽象与旧版并行发布,现有集成持续可用,直到其负责人迁移;弃用时应公布日落期限,绝不通过原地变更强制执行。
- 为增长设计列表端点——采用不透明游标分页(对
(sort_key, id)位置进行编码,在列表封装中返回has_more+next_cursor),而非偏移量分页;并发写入时,偏移量分页会跳过和重复行,翻到深页时也会失效。筛选和排序应仅允许使用索引字段对应的命名参数,绝不可将参数直接传递给查询层。 - 让不安全方法可安全重试——变更操作接受
Idempotency-Key;相同键 + 相同请求体会重放已存储的响应,相同键 + 不同请求体则返回 409。没有此机制,每次客户端重试都可能导致副作用重复。(重试分类和封装格式:awesome-error-standards。) - 明确并发和部分更新规则——可能发生冲突的更新采用乐观并发控制(
ETag+If-Match,版本不匹配时返回 409 和当前版本);声明 PATCH 语义(合并补丁还是替换),并拒绝空 PATCH,而不是静默地将其视为无操作。 - 将契约写成事实来源——使用能够生成或验证实现的 OpenAPI/schema 文档,而不是会与实现逐渐偏离的说明文字。ID 应不透明且带前缀(
ord_…),时间戳使用 ISO 8601 UTC,金额使用带币种的整数最小货币单位,枚举应为封闭集合,并为使用方规定遇到未知值时的默认处理方式。
设计评审清单
评审现有设计时,请按照相同的决策逐项检查并记录发现:
- 所有端点的命名和大小写风格一致;负载中不得混用
camelCase/snake_case。 - 资源路径中不使用动词,除非表示经过建模的状态转换;REST 资源旁不得出现 RPC 风格的
/getX端点。 - 从一开始就为每个列表端点添加分页——事后补上分页属于破坏性变更。
- 每项变更都必须支持按键幂等,或明确说明其天然幂等。
- 错误响应必须引用一个稳定的错误格式规范(awesome-error-standards),不得由各端点自行发挥。
- 明确说明版本控制和弃用策略;不得说“等破坏兼容性时再决定”。
- API 发出的 Webhook 属于公开接口:必须签名、版本化、支持重新投递,并遵循与端点相同的契约规范。
- 响应中不得包含客户端不应看到的内容:内部 ID、标记,或因序列化器默认行为而泄露的字段。
变更评审
当输入是对已发布契约的变更(对规范、架构、处理程序或负载的差异)时,应评审变更本身,而不是最终状态。不能仅凭新架构判断兼容性:并排查看新旧结构,以及数据传输链路两端的代码。检查以下四种场景,并记录每种场景的结果:
- 旧写入方,新读取方:变更前生成的负载到达变更后的代码。
- 新写入方,旧读取方:变更后生成的负载到达尚未更新的代码(仍在部署的服务器、已发布的移动端或 SDK 构建版本)。
- 回滚:旧代码恢复运行时,新代码写入的值已经存储或正在传输。
- 静态负载:队列、缓存条目、已存储的行和文件中的旧格式消息由新代码读取;回滚后,新格式消息由旧代码读取。
在所有可访问的代码库中,双向搜索使用方:
- 客户端读取某个字段,但服务器已停止发送该字段、更改了字段名称或类型。这类问题会静默地导致错误:不会报错,只会缺少值或得到错误的值。
- 服务器返回客户端没有对应处理分支的状态码或错误码。
- 请求字段变为必填,或校验规则收紧,但现有调用方未提供该字段。
- 新的枚举值传递给使用穷尽式 switch 或封闭类型的使用方。
“未找到使用方”不等于“未使用”:外部客户端、其他代码库、脚本和已存储数据无法通过代码搜索发现。说明搜索了哪些内容,并将其余部分视为未知。
每项变更给出一个结论:兼容(四种场景均通过,并引用证据)、依赖顺序(仅在明确说明的部署顺序或迁移步骤下安全;指出具体条件)、破坏兼容性(某种场景会影响正在使用的调用方;解决方案是新版本或分层结构)、无法确定(某种场景或使用方范围无法检查;指出缺少的信息,绝不将其上调为“兼容”)。
输出
API Design — <scope> — <date>
Contract decisions:
- <area: resources | versioning | pagination | idempotency | concurrency> — <decision> — <the consumer consequence it buys>
...
Spec: <OpenAPI/schema skeleton or diff, when producing one>
Deviations from incumbent conventions: <each with its justification, or "none">
Open questions: <decisions needing the user, with a recommended default each>
交付前自查
- 根据上方的设计评审清单检查自己的输出——发现自身设计存在问题时,应在交付前修复,而不是附带保留说明后发布。
- 每项契约决策都要说明它为使用方带来的影响;“因为这是最佳实践”不是理由——删除或给出依据。
- 第 1 步必须有证据:指出实际检查过的现有规范、路由或客户端代码。对已有约定的 API 直接套用默认值,正是要避免的问题。
- 每项变更都要回答重试问题(说明使用幂等键还是天然幂等);每个列表端点都要回答增长问题。
- 每个待解决问题都要附上建议的默认方案;只提出问题会把设计工作推回给读者。
反模式
| 反模式 | 应采用的做法 |
|---|---|
| 对不断增长的表进行偏移分页 | 使用基于索引排序键的不透明游标 |
| 将破坏性变更隐藏为“修复” | 发布新版本;客户端使用的旧结构永不改变 |
| 每个端点使用不同的错误结构 | 使用统一信封,并由客户端共享一个解析器 |
| URL 中使用连续整数 ID | 使用带前缀的不透明 ID;序列会泄露数据量,并容易被枚举 |
PUT 静默丢弃未知字段 | 明确声明 PATCH 语义;严格验证并明确指出拒绝原因 |
| 落后于实现的说明文档规范 | 使用机器可读的契约来生成代码或限制代码 |
| 围绕首个客户端的屏幕进行设计 | 资源应对领域建模;视图组合应由客户端负责 |
评论
支持 Markdown