API 设计
优秀的 API 是一份契约,让你能够在不断裂依赖方的前提下持续演进。先设计契约,对资源(而非行为)进行建模,按规范使用 HTTP,保持向后兼容,并将安全性和可操作性作为默认项——而非事后补救。
本技能在工作流层面与协议无关;规范细节存放在 references/ 中,按需加载。
始终先读
列出 learnings/ 并阅读所有文件名看起来与当前任务相关的文件——项目的版本策略、错误信封、认证模型、命名约定以及特定于提供商的 Webhook 特殊规则都保存在那里,它们会覆盖此文件中的默认值。如果某个学习记录与本 SKILL.md 冲突,以学习记录为准——请告知用户。
权衡——何时严格应用,何时适当放宽
对于任何有外部或跨团队消费者、或需要持久化状态的 API,请严格应用整套规范。对于完全由你控制两端且可以原子化变更的私有单消费者端点,可以适当放宽流程——但永远不要降低安全底线(授权、输入验证、URL 中不能包含密钥)。
工作流
-
刻意选择协议。 REST 适用于资源 CRUD 和广泛的客户端接入;GraphQL 适用于客户端需要对图进行灵活、客户端定制的查询;gRPC 适用于低延迟的内部服务间通信。不要因为 GraphQL/gRPC 是新事物就默认使用它们。说明选择结果及其理由。 → 验证:你能够说出消费者的名称以及该协议为何适合它。
-
契约优先设计。 在编写代码之前编写 OpenAPI(或 proto/SDL)规范;审查它,然后根据规范生成或手写处理器。在 CI 中对规范进行 lint(例如使用 Spectral),以便漂移导致构建失败。 → 验证:规范存在并且是事实来源,而非从代码反向推导而来。
-
对资源(名词)建模,而非行为。 资源是具有标识的事物;集合使用复数形式。不常见的操作应成为子资源或状态,而不是路径中的动词。参见
references/rest-conventions.md。 → 验证:任何路径中都不含动词。 -
正确使用 HTTP 方法和状态码语义。 GET/HEAD 安全且幂等,PUT/DELETE 幂等,PATCH 部分更新,POST 创建/非幂等。状态码传达结果;消息体携带数据或错误。参见
references/rest-conventions.md。 → 验证:不存在200 OK但消息体中包含错误的情况。 -
始终应用一致的约定。 路径使用一种大小写规则,字段使用一种;集合使用复数形式;使用不透明标识符(不泄露数据库序列/结构)。不一致性是对每个消费者的永久负担。 → 验证:读者能够预测从未见过的端点形状。
-
绝不破坏消费者。 同一版本中只做增量的变更;破坏性变更需要新版本以及带有
Deprecation/Sunset头部的弃用窗口。对之前无分页的列表增加分页属于破坏性变更。参见references/versioning-and-compatibility.md。 → 验证:每次变更在发布之前都需分类为增量或破坏性。 -
标准化错误。 使用 RFC 9457
application/problem+json,并携带稳定的机器可读code。HTTP 状态码必须与消息体一致。绝不泄露堆栈跟踪。参见references/errors-idempotency-concurrency.md。 → 验证:每个错误路径都返回相同的信封形状和稳定的 code。 -
从第一天起对所有列表进行分页。 优先使用基于游标的分页(
page_size+page_token/next链接)而非偏移量。后期补充分页会破坏客户端。 → 验证:不存在无界列表端点。 -
确保写入操作可以安全重试。 对于非幂等的 POST 使用
Idempotency-Key(存储并重放首次响应);配合ETag+If-Match使用乐观并发控制,出现冲突时返回412。参见references/errors-idempotency-concurrency.md。 → 验证:在超时后重试 POST 的客户端不会重复扣费/重复创建。 -
默认安全。 仅使用 TLS;服务端验证令牌;基于对象的授权(而非仅角色);URL 中不能包含密钥;根据 Schema 验证输入;限速;CORS 白名单。参见
references/security.md。 → 验证:每个引用对象的请求都要检查对象级授权。 -
记录学习(最后一步)。 如果你遇到了此文件或
references/中尚未记录的约定、约束或陷阱,请追加一个learnings/YYYY-MM-DD-slug.md。否则跳过。
反面模式(见即拒绝)
- 路径中的动词 ——
POST /createUser、/getOrders。请使用POST /users、GET /orders。 200 OK但消息体中包含错误。 状态代码即是结果;客户端和代理依赖它。- 泄露内部/数据库模型 —— 将列名、内部枚举、序列 ID 或连接结构暴露为公有形态。应映射为刻意设计的 DTO。
- 聊天式端点 / N+1 —— 强制客户端进行多次往返才能拼装一个视图。提供扩展字段(
expand=)或更粗粒度的资源。 - 无新版本的破坏性变更 —— 包括重命名/删除字段、更改类型或默认值,或对之前无分页的列表增加分页。
- 不一致的约定 —— 大小写混合、集合的单复数混用、有时包裹有时不包裹的响应。
- 无幂等键的非幂等 POST —— 任何重试都会重复创建。
- URL 中的敏感数据 —— 路径或查询中的令牌、PII、密钥;它们会出现在日志、历史和 Referer 中。
- 缺少对象级授权(BOLA) —— 检查了身份验证,但未检查当前调用方是否有权访问当前对象。OWASP API #1。
- 暴露堆栈跟踪 —— 错误消息体中的内部类名、文件路径、SQL。
- 无界列表端点 ——
GET /events返回所有数据。
记录学习
完成后,询问自己:我是否遇到了本 SKILL.md 或 references/ 中未包含的约定、约束或陷阱?(例如项目特定的版本策略、组织已标准化的非标准错误信封、Webhook 提供商的签名方案、强制要求的头部)。如果是,请按照 learnings/README.md 追加一个 learnings/YYYY-MM-DD-slug.md。如果否,则跳过——不要制造噪音。
参见
references/rest-conventions.md—— 资源建模、命名、方法、状态码、分页、过滤、缓存。references/versioning-and-compatibility.md—— 版本策略、增量与破坏性变更、弃用策略。references/errors-idempotency-concurrency.md—— problem+json、幂等键、乐观并发、重试、长时间运行操作、Webhook。references/security.md—— TLS、OAuth2/OIDC、BOLA/BFLA、输入验证、限速、CORS、数据暴露。developing-features技能 —— 通用的生产规则和完整安全参考(API 表面只是多个边界之一)。reviewing-code技能 —— 审查修改 API 表面的 diff。documenting-features技能 —— 记录 API 契约、ADR 以及与代码并存的领域模型。learnings/—— 随时间积累的项目特定 API 约定。