SuperAPI AI 工具集

api-and-interface-design

编程与开发

查看项目

更新于 10-16 08:53

技能介绍

用途

本技能用于指导稳定、文档清晰且难以误用的接口设计。好的接口让正确的事情容易做,让错误的事情难做。它适用于 REST API、GraphQL schema、模块边界、组件 props,以及任何一段代码与另一段代码对话的表面。

何时使用

  • 设计新的 API endpoint
  • 定义模块边界或团队之间的契约
  • 创建组件 prop 接口
  • 建立会影响 API 形态的数据库 schema
  • 修改现有公共接口

核心原则

Hyrum's Law

只要 API 用户足够多,系统中所有可观察行为都会被某些人依赖,无论你在契约中承诺了什么。每个公共行为,包括未记录的怪癖、错误消息文本、时序和排序,一旦被用户依赖,就会成为事实契约。设计含义:有意识地决定暴露什么;不要泄漏实现细节;在设计时规划废弃;测试还不够,即使有完美的契约测试,Hyrum's Law 也意味着“安全”的改动仍会破坏依赖未记录行为的真实用户。

单版本规则

避免迫使消费者在同一个依赖或 API 的多个版本之间选择。当不同消费者需要同一事物的不同版本时,就会出现 diamond dependency 问题。为一次只存在一个版本的世界设计:扩展,而不是 fork。

1. 契约优先

先定义接口,再实现它。契约就是规格,实现随后而来。示例中先定义 TaskAPI 接口,包含 createTask、listTasks、getTask、updateTask、deleteTask 等方法及其输入输出类型。

2. 一致的错误语义

选择一种错误策略,并在所有地方使用它。REST 使用 HTTP status codes 加结构化 error body,每个错误响应遵循同一形态,包含机器可读的 code、人类可读的 message 以及可选的 details。状态码映射:400 客户端数据无效、401 未认证、403 已认证但未授权、404 资源未找到、409 冲突、422 验证失败、500 服务器错误(绝不暴露内部细节)。不要混用模式,如果有些 endpoint throw、有些返回 null、有些返回 { error },消费者就无法预测行为。

3. 在边界验证

信任内部代码,在外部输入进入系统的边缘进行验证。验证应放在 API route handlers、Form submission handlers、外部服务响应解析、环境变量加载。第三方 API 响应是不可信数据,在任何逻辑、渲染或决策中使用前,先验证其形态和内容。验证不应该放在共享类型契约的内部函数之间、被已验证代码调用的 utility functions 中、刚从自己数据库中取出的数据上。

4. 优先添加,而不是修改

扩展接口时不要破坏现有消费者。添加可选字段是好的;改变现有字段类型或移除字段是坏的。

5. 可预测命名

REST endpoints 使用复数名词不用动词;Query params 使用 camelCase;Response fields 使用 camelCase;Boolean fields 使用 is/has/can 前缀;Enum values 使用 UPPER_SNAKE。

6. 兑现 Idempotency Key

接受 Idempotency-Key 是契约,兑现它才是实现。从意图派生 key,而不是从单次尝试;key 来自客户端或发起事件,绝不来自负责重试的那一层。原子地认领,先检查再执行是一场 race,应让 unique constraint 决定胜者。守住 payload,同一个 key 配上不同的 body 必须大声报错失败。想清楚 in-flight 的重复请求会得到什么,可选策略包括拒绝(409 Conflict)、等待(阻塞等待结果并设置上限)、返回 pending(202 + status URL)。每次调用有三种结果:成功、失败以及未知。保留期取决于最长的 retry chain,而不是磁盘成本。

REST API 模式

资源设计示例:GET /api/tasks 列出任务、POST /api/tasks 创建任务、GET /api/tasks/:id 获取单个任务、PATCH /api/tasks/:id 部分更新、DELETE /api/tasks/:id 删除任务,以及子资源 comments 的 GET 与 POST。

分页:对 list endpoints 分页,请求使用 page、pageSize、sortBy、sortOrder,响应包含 data 与 pagination(page、pageSize、totalItems、totalPages)。

过滤:使用 query parameters 表示 filters,如 status、assignee、createdAfter。

部分更新(PATCH):接受 partial objects,只更新提供的字段。

TypeScript 接口模式

  • 对变体使用 Discriminated Unions,每个变体显式声明,消费者可获得类型收窄。
  • 输入/输出分离:Input 是调用方提供的,Output 是系统返回的(包含服务器生成字段)。
  • 对 ID 使用 Branded Types,防止意外把 UserId 传到期望 TaskId 的位置。

常见自我合理化

资料列举了多种自我合理化及其现实,例如“我们之后再写 API 文档”(类型本身就是文档,先定义它们)、“现在还不需要分页”(一旦有人有 100+ items 立刻就需要)、“PATCH 很复杂,我们直接用 PUT”(PUT 要求每次传完整对象,PATCH 才是客户端真正想要的)、“需要时再给 API 做版本化”(没有版本化的 breaking changes 会破坏消费者)、“没人用那个未记录行为”(Hyrum's Law)、“我们可以同时维护两个版本”(多版本会放大维护成本并制造 diamond dependency 问题)、“内部 API 不需要契约”(内部消费者也是消费者)、“接受了 Idempotency-Key header 就足够了”(header 是契约,把 key 与结果存在一起才是实现)、“我们的 queue 保证 exactly-once 投递”(consumer 崩溃时没有任何 queue 能做到)、“重复请求很罕见”(retries 恰好在依赖降级时激增)。

危险信号

Endpoint 根据条件返回不同形态;Endpoint 之间错误格式不一致;验证散落在内部代码中而不是位于边界;对现有字段做 breaking changes;List endpoints 没有分页;REST URL 中有动词;未经验证或清理就使用第三方 API 响应;用 SELECT 查 idempotency key 后再 INSERT;idempotency key 由 UUID、时间戳或任何每次尝试都重新生成的值派生;接受同一个 key 配不同 request body 却悄悄返回第一次的响应;key 保留窗口短于可能重新投递请求的最长路径。

验证清单

设计 API 后应检查:每个 endpoint 都有 typed input 和 output schemas;错误响应遵循单一一致格式;只在系统边界进行验证;List endpoints 支持分页;新字段是增量且可选的;命名在所有 endpoints 中遵循一致约定;API 文档或类型与实现一起提交;会改变状态的 endpoints 要么兑现 idempotency key,要么在文档中注明重试不安全;key 的认领是一个原子操作并由 unique constraint 保护;复用的 key 配上不同 payload 时大声失败;对 in-flight 重复请求的响应是有意选择的;key 保留期长于最长的 retry path,包括 dead-letter 重放。