mcpField
A
api-design

api-design

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

关于 api-design

用于设计服务 API 合约的架构技能,涵盖协议、分页、重试和版本管理。帮助系统设计者将模糊的接口需求转化为具体且经压力检验的决策。

主要功能

  1. 01按调用模式选择协议
  2. 02分页策略建议
  3. 03幂等写入与重试合约
  4. 04错误与版本管理策略
  5. 05压力与容量考量

适用场景

  1. 01定义新服务的接口
  2. 02为变化中的数据集选择分页方式
  3. 03避免写入重试产生重复效果
  4. 04规划兼容的 API 演进

安装

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

$npx skills add https://github.com/proyecto26/system-design-skills --skill api-design

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

包含的文件

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

references/
deep-dive.md
references/providers/
aws.md
azure.md
gcp.md
generic.md

详细介绍

API 设计

定义客户端与服务之间的契约:精确的请求和响应格式、调用方如何分页获取数据、如何安全地重试写入、如何报告错误,以及契约如何演进。如果这一点含糊不清,后续的架构图就只能靠猜(GUIDE #8)——在明确请求、响应和对应的键之前,“NoSQL 盒子”或“用户服务”什么问题也解决不了。

何时使用

服务和数据存储已经确定,现在需要定义接口:客户端发送什么、收到什么、如何获取下一页、如何重试付款而不重复扣款,以及 v1 客户端如何适应 v2 部署。只要有人在没有明确格式的情况下说“获取动态”或“存储帖子”,就该考虑使用此技能。

何时不使用

需求和规模尚未明确时——协议选择取决于读写比和延迟目标,而非个人偏好(→ requirements-scoping、back-of-the-envelope)。不要因为 gRPC、GraphQL 或流式传输听起来更现代就贸然采用;普通的 REST/JSON 端点是满足大多数约束的成本最低的契约,而命名你并不需要的高级协议则是 YAGNI 的危险信号。内部数据访问键和分区设计属于 data-storage;此技能设计与之相对应的外部契约。

先明确需求

  • 调用形式 — 请求/响应(CRUD)、双向/实时,还是一次请求 → 多个结果(流式传输)?这决定了协议。
  • 读写比和结果集大小 — 这会影响分页方式,以及读取是否需要单独的优化路径(→ back-of-the-envelope)。
  • 重试安全性 — 写入能否安全地重复执行?哪些操作天然幂等(PUT/DELETE),哪些不是(用于创建/扣费的 POST)?
  • 客户端多样性和迭代频率 — 面向升级较慢、需要严格版本控制的公共第三方,还是与你自己的应用一同发布?
  • 延迟和负载预算 — 移动网络/高延迟连接适合紧凑的二进制格式和较少的往返;浏览器则适合可缓存的 HTTP。

可选方案

协议 / 风格(根据调用形式选择)

  • 基于 HTTP/JSON 的 REST — 使用标准 HTTP 方法执行资源 CRUD。适合作为面向公众、可缓存且兼容浏览器的 API 默认方案。
  • RPC / gRPC(HTTP/2、protobuf) — 类型化方法调用、紧凑的二进制格式、流式传输。适用于对延迟和架构契约有要求的内部服务间通信。
  • GraphQL — 客户端在一次查询中精确指定所需字段。适用于许多客户端需要获取同一数据图的不同格式,且 REST 的过度或不足获取会造成问题的场景。
  • WebSocket / SSE(流式传输) — 服务器持续向客户端推送数据。适用于服务器必须推送更新的场景(聊天、在线状态、实时动态)——参见下方的轮询层级。
  • Webhooks — 事件发生时,服务器调用客户端的 URL。适用于向第三方异步传递事件。

服务器推送层级(客户端需要获取最新数据时)

  • 轮询 → 长轮询 → SSE → WebSocket:推送效率依次提高,连接成本也随之增加。从轮询开始;仅当具体指标(更新频率、扇出规模)迫使你升级时才采用更高层级。

分页

  • 游标(键集分页) — 基于稳定排序键的不透明令牌。大型或持续变化的数据集默认采用此方式。
  • 偏移量/条数 — ?offset=40&limit=20。仅用于较小、基本静态且需要跳转到指定页的列表。

幂等性 — 客户端在不安全的写入请求中发送 Idempotency-Key;服务器对重试请求去重。此技能负责定义键的契约(参见“接口草图”)。

版本控制 — URI(/v2/...)、请求头(Accept: application/vnd.x.v2+json),或采用只增不破坏的方式。优先采用只增不破坏的方式;仅在发生破坏性变更时才保留新版本。

权衡

选项解决的问题带来的问题何时更改
REST/JSON通用、可缓存、简单,工具支持广泛过度/不足获取;用于图数据时请求繁杂;类型较弱字段定制带来困扰 → GraphQL;内部延迟较高 → gRPC
gRPC/RPC紧凑、类型明确、快速,原生支持流式传输非浏览器原生;需要代理;对 HTTP 缓存不透明公共/浏览器客户端需要 → REST 网关
GraphQL一次往返,客户端选择字段缓存/限流困难;查询开销大(N+1);服务端复杂形态固定且很少(REST 更简单),或查询成本不受限
WebSocket/SSE真正的服务端推送、低延迟更新有状态连接,扩展/负载均衡更困难,需要重连逻辑更新不频繁 → 长轮询;仅单向通信 → SSE
游标分页插入数据时仍保持稳定;任意深度下每页均为 O(1)令牌不透明;无法随机跳页;需要排序键用户必须跳转到静态数据集的第 N 页 → 偏移分页
偏移分页简单;可任意跳页插入时数据漂移/重复;较大的偏移量需要扫描,速度较慢数据集增长或变化 → 游标分页
幂等键可安全重试;不会重复扣费服务端必须存储键并去重;需定义键的 TTL/作用范围操作本身具有幂等性(PUT/DELETE)→ 可考虑跳过
URI 版本控制明确、缓存/日志可见、易于路由版本泛滥;客户端永久固定在某个版本变更为增量式 → 无需新版本

压力下的行为

契约决定客户端会在多大程度上放大故障。

  • 重试风暴。 超时的非幂等写入会被重试,可能导致重复执行;客户端同步重试会让恢复中的服务遭遇请求洪峰。 幂等键可确保重试安全;负责通过退避/抖动来错开重试的是 resilience-failure。提供 429 + Retry-After,让行为良好的客户端放慢速度。
  • 深度分页。 大偏移量下的偏移分页会迫使存储扫描并丢弃行——一个看似开销很低的端点,在爬虫访问时会变成全表扫描。 游标分页可确保每页的开销始终为 O(页大小)。
  • 无界响应。 没有默认页大小、没有最大负载限制,或 GraphQL 查询产生大量扇出 → 单个请求就能耗尽内存/CPU。在契约中限制页大小、深度和复杂度。
  • 连接耗尽。 WebSocket/SSE 会为每个客户端占用一个连接;部署后发生的重连风暴可能耗尽文件描述符和负载均衡器槽位。规划带抖动的重连策略,并设置连接数限制。
  • 版本变更导致故障。 对共享数据结构进行非增量式变更会同时破坏所有客户端——这是最严重的自我引发故障。采用增量式变更;通过版本进行弃用。

监控: 按状态类别(4xx 与 5xx)统计错误率、各端点的 p99 延迟、重试/幂等重放率、分页深度分布、打开的连接数,以及各版本的流量(以了解何时可以停用旧版本)。

如何应用

  1. 明确输入。 确定调用形式、读写比例、结果集大小、重试安全性、客户端多样性,以及延迟和负载预算(参见 先明确)。了解这些信息前,不制定契约。
  2. 根据权衡表选择协议和分页方式。 默认使用 REST/JSON;只有输入条件要求时,才升级到 gRPC(内部延迟和类型安全)、GraphQL(字段形态多样)或推送层级(服务端必须主动推送)。默认使用游标分页;仅对较小、静态且需要跳转到指定页面的集合使用偏移分页。
  3. 设置关键参数。 根据安全性选择幂等动词(GET/PUT/DELETE 可安全重试,POST 不可),定义 Idempotency-Key 契约及其 TTL,固定稳定的错误封装,并选择版本策略(默认采用增量式变更)。
  4. 对契约进行压力测试。 限制默认和最大页面大小、GraphQL 深度/复杂度及负载大小;为推送制定带随机抖动的重连方案和连接数限制;返回 429 + Retry-After。确认 v1 客户端在部署 v2 后仍能正常运行。
  5. 用数字估算规模。 估算每个端点的请求数/秒、响应大小、每个会话的页面数,以及更新频率 × 扇出;用这些数据确认协议选择和轮询与推送的取舍(→ back-of-the-envelope)。
  6. 选择提供商。 除非用户指定云服务,否则沿用通用方案;若用户指定了云服务,则映射到其托管网关(参见 选择提供商)。
  7. 在答案中展示权衡——提供一张 Decision | Solves | Worsens | Change it when… 表格,每项选择占一行(例如,所选方案、存储、故障策略);最后一列填写具体触发条件(一个数字或可观测信号)。然后指出最先会出现故障的环节。

应做和不应做

应做

  • 默认使用 REST/JSON 和游标分页;只有数字指标要求时才升级。
  • 根据重试安全性选择动词,确保重试从设计上就是正确的。
  • 要求每个非幂等写入都携带 Idempotency-Key,并限制其 TTL。
  • 限制页面大小、查询深度/复杂度和负载,确保最坏情况下的成本可控。
  • 在每个端点中保持统一、稳定的错误封装(code、message、request_id、retryable)。

不应做

  • 仅仅因为 gRPC、GraphQL 或流式传输听起来很现代就采用它们(这是 YAGNI 的警示信号)。
  • 不要对不断增长或不断变化的集合使用偏移分页——深层偏移会扫描存储。
  • 不要在共享数据结构上发布非增量式变更,却不引入新版本。
  • 不要让响应大小不受限(没有默认页面大小,也没有最大负载大小)。
  • 不要在此处重复讲解重试/退避或分片——链接到 resilience-failure / data-storage。

重要数字

选择前先估算:每个端点的请求数/秒、平均和最大响应大小、页面大小 × 每个会话的页面数(影响读取负载),以及更新频率 × 扇出(决定轮询还是推送)。以下几条经验法则可能改变选择:二进制协议(protobuf)通常能将负载缩小数倍;在数据上网线前进行压缩还能进一步缩小负载——这两者在移动网络或高延迟链路上最为重要,因为此时往返延迟占主导。对 N 个客户端以 T 为间隔轮询,每秒会产生 N/T 个几乎总是空的响应请求;当这个数接近推送层级的连接数预算时,应切换到 SSE/WebSocket。限制页面大小(以及 GraphQL 深度/复杂度),确保最坏情况下的响应成本可控;同时限制幂等键的存储窗口(例如 24h),避免去重表无限增长。规范的延迟/QPS/负载参考数据请查看 back-of-the-envelope——不要在此处重复其表格。

接口示例

明确契约。一个使用游标分页和错误封装的读取示例:

GET /v1/users/{id}/posts?limit=20&cursor=eyJ0cyI6MTciLCJpZCI6Ijk5In0
200 OK
{ "data": [ { "id": "p_881", "created_at": "...", "text": "..." } ],
  "next_cursor": "eyJ0cyI6...",          // null when no more pages
  "has_more": true }

# error envelope — stable shape across every endpoint
4xx/5xx
{ "error": { "code": "rate_limited", "message": "…", "request_id": "req_…",
             "retryable": true } }

一个幂等写入示例(本技能负责的契约):

POST /v1/payments
Idempotency-Key: 9f1c-… (client-generated UUID, unique per logical operation)
{ "amount": 4200, "currency": "usd", "source": "card_…" }

# Server: first request with a key → execute, store (key → response) for a TTL.
# Retry with same key → return the stored response, do NOT re-execute.
# Same key + different body → 422 (key reuse conflict).

让响应结构与访问模式相匹配:游标应编码存储分页所用的分区/排序键(PK/SK 设计由 data-storage 负责)。根据安全性选择动词——GET(安全)、PUT/DELETE(幂等)、POST(非幂等),确保重试从设计上就是正确的。

选择提供商

默认采用上述通用方案。如果用户指定了云服务商,请阅读 references/providers/<provider>.md,了解托管服务映射、 配额/限制和提供商特定的权衡。如果该提供商没有对应文件,则采用通用方案。

图示

要将请求/响应路径、游标分页循环或幂等重试序列(客户端 → 网关 → 服务 → 存储,其中包含重放分支)可视化,请使用插件内的 architecture-diagram 技能——不要在此处嵌入 Mermaid。快速推理时,可以内联使用一行 ASCII 草图。

相关构建模块

  • data-storage — 依赖于它:请求/响应和游标的形状与其主键和访问模式相对应(分片/分区在该模块中介绍);请一并设计。
  • resilience-failure — 与它配合使用:该模块负责重试/退避/抖动、超时和速率限制,而幂等键(由此模块负责)则确保这些重试安全。
  • consistency-coordination — 当重试或并发写入不得违反不变量时,与它配合使用;CAP/仲裁理论在该模块中介绍。
  • messaging-streaming — 当契约是异步事件或 webhook 时,可作为同步请求/响应的替代方案。
  • system-design — 为此提供输入:编排器在接口步骤中路由到此处。

参考资料

  • references/deep-dive.md — 协议机制(HTTP 动词/状态码规范、gRPC 流模式、GraphQL 查询成本限制)、游标令牌构造、完整的幂等键状态机、版本管理/弃用策略和错误契约设计。详细设计契约时请阅读。
  • references/providers/{generic,aws,azure,gcp}.md — API 网关/托管端点映射、会影响决策的限制,以及各环境中的注意事项。

评论

支持 Markdown