api-design
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseAPI Design
API设计
Define the contract between clients and a service: the exact request and response
shapes, how callers page through data, how retried writes stay safe, how errors
are reported, and how the contract evolves. Get this vague and the rest of the
diagram is guesswork (GUIDE #8) — a "NoSQL box" or "user service" solves nothing
until you can write the request, the response, and the key it hits.
定义客户端与服务之间的契约:明确的请求和响应结构、调用方如何分页获取数据、重试写入操作时如何保证安全、错误如何上报,以及契约如何演进。如果这部分定义模糊,后续的架构设计全是猜测(指南#8)——一个「NoSQL模块」或「用户服务」毫无意义,除非你能写出具体的请求、响应,以及它命中的键。
When to reach for this
何时使用
The design has named services and a datastore, and you now need the interface:
what a client sends, what it gets back, how it fetches the next page, how it
retries a payment without double-charging, and how a v1 client survives a v2
deploy. Reach here the moment someone says "fetch the feed" or "store the post"
without a shape.
当设计中已明确服务和数据存储,且需要确定接口时:客户端发送什么、接收什么、如何获取下一页数据、如何重试支付操作避免重复扣费、v1客户端如何在v2版本部署后正常运行。当有人提到「获取信息流」或「存储帖子」却未明确结构时,就该使用本技能。
When NOT to
何时不使用
Before requirements and scale are pinned — the protocol choice depends on
read/write ratio and latency target, not taste (→ ,
). Don't reach for gRPC, GraphQL, or streaming because they
sound modern; a plain REST/JSON endpoint is the cheapest contract that meets most
constraints, and naming a fancier protocol you don't need is a YAGNI red flag.
Internal data-access keys and partition design live in ; this skill
designs the external contract that mirrors them.
requirements-scopingback-of-the-envelopedata-storage在需求和规模未明确前——协议的选择取决于读写比和延迟目标,而非个人偏好(→ 、)。不要因为gRPC、GraphQL或流式传输听起来时髦就选用;普通的REST/JSON端点是满足大多数约束的最低成本契约,选用不需要的复杂协议是YAGNI(You Aren't Gonna Need It)的危险信号。内部数据访问键和分区设计属于范畴;本技能负责设计与它们对应的外部契约。
requirements-scopingback-of-the-envelopedata-storageClarify first
先明确以下问题
- Call shape — request/response (CRUD), bidirectional/real-time, or one request → many results (streaming)? This picks the protocol.
- Read/write ratio and result-set size — drives pagination and whether reads
need their own optimized path (→ ).
back-of-the-envelope - Retry safety — can a write be safely repeated? Which operations are naturally idempotent (PUT/DELETE) vs not (POST that creates/charges)?
- Client diversity & churn — public third parties (slow to upgrade, need strict versioning) vs your own apps (ship together)?
- Latency & payload budget — mobile/high-latency links favor compact binary and fewer round trips; browsers favor cacheable HTTP.
- 调用结构——请求/响应(CRUD)、双向/实时,或是单请求→多结果(流式)?这决定了协议的选择。
- 读写比与结果集大小——影响分页策略,以及读取操作是否需要独立的优化路径(→ )。
back-of-the-envelope - 重试安全性——写入操作能否安全重复执行?哪些操作天然具有幂等性(PUT/DELETE),哪些没有(创建/扣费的POST)?
- 客户端多样性与更新频率——是第三方公共客户端(升级缓慢,需要严格的版本控制)还是自有应用(同步发布)?
- 延迟与负载预算——移动/高延迟链路更适合紧凑的二进制格式和更少的往返次数;浏览器更倾向于可缓存的HTTP。
The options
可选方案
Protocol / style (pick per call shape)
- REST over HTTP/JSON — resource CRUD over standard verbs. Use as the default for public, cacheable, browser-friendly APIs.
- RPC / gRPC (HTTP/2, protobuf) — typed method calls, compact binary, streaming. Use for internal service-to-service traffic where latency and schema contracts matter.
- GraphQL — client specifies exactly the fields it wants in one query. Use when many clients need different shapes of the same graph and over/under-fetching on REST hurts.
- WebSocket / SSE (streaming) — persistent server→client push. Use when the server must push updates (chat, presence, live feeds) — see the polling tier below.
- Webhooks — server calls the client's URL on an event. Use for async, third-party event delivery.
Server-push tier (when clients need fresh data)
- Polling → long-polling → SSE → WebSocket, in increasing efficiency for push and increasing connection cost. Start at polling; escalate only when a number (update frequency, fanout) forces it.
Pagination
- Cursor (keyset) — opaque token over a stable sort key. Default for large or changing datasets.
- Offset/limit — . Use only for small, mostly-static, jump-to-page lists.
?offset=40&limit=20
Idempotency — client sends an on unsafe writes; the server
dedupes retries. This skill owns the key contract (see Interface sketch).
Idempotency-KeyVersioning — URI (), header (), or
additive/never-break. Prefer additive; reserve a new version for breaking changes.
/v2/...Accept: application/vnd.x.v2+json协议/风格(根据调用结构选择)
- REST over HTTP/JSON——基于标准动词的资源CRUD操作。作为公共、可缓存、浏览器友好型API的默认选择。
- RPC / gRPC(HTTP/2, protobuf)——类型化方法调用、紧凑二进制格式、流式传输。适用于内部服务间通信,尤其是对延迟和 schema 契约有要求的场景。
- GraphQL——客户端在一次查询中指定所需的精确字段。当多个客户端需要同一数据图谱的不同结构,且REST的过度/不足获取问题严重时使用。
- WebSocket / SSE(流式)——持久化的服务器→客户端推送。当服务器必须主动推送更新(聊天、在线状态、实时信息流)时使用——详见下方的推送层级。
- Webhooks——服务器在事件发生时调用客户端的URL。适用于异步第三方事件推送。
服务器推送层级(当客户端需要实时数据时)
- 轮询 → 长轮询 → SSE → WebSocket,推送效率逐渐提升,但连接成本也逐渐增加。从轮询开始;只有当数据更新频率、客户端数量等指标要求时,才升级到更高层级。
分页策略
- 游标(键集)分页——基于稳定排序键的不透明令牌。适用于大型或频繁变化的数据集,作为默认选择。
- 偏移/限制分页——。仅适用于小型、基本静态、支持跳转至指定页码的列表。
?offset=40&limit=20
幂等性——客户端在不安全的写入操作中发送;服务器去重重试请求。本技能负责定义该键的契约(见接口示例)。
Idempotency-Key版本控制——URI()、请求头(),或增量式/永不兼容的方式。优先选择增量式;仅在有破坏性变更时才启用新版本。
/v2/...Accept: application/vnd.x.v2+jsonTrade-offs
权衡对比
| Option | What it solves | What it worsens | Change it when |
|---|---|---|---|
| REST/JSON | Universal, cacheable, simple, tooling everywhere | Over/under-fetch; chatty for graphs; weak typing | Field-shaping pain → GraphQL; internal latency → gRPC |
| gRPC/RPC | Compact, typed, fast, native streaming | Not browser-native; needs proxy; opaque to HTTP caches | Public/browser clients need it → REST gateway |
| GraphQL | One round trip, client picks fields | Caching/rate-limiting hard; expensive queries (N+1); server complexity | Few fixed shapes (REST simpler) or query cost unbounded |
| WebSocket/SSE | True server push, low-latency updates | Stateful connections, harder to scale/LB, reconnection logic | Updates are infrequent → long-poll; one-way only → SSE |
| Cursor pagination | Stable under inserts; O(1) per page at any depth | Opaque token; no random page jump; needs sort key | Users must jump to page N of a static set → offset |
| Offset pagination | Trivial; arbitrary page jumps | Drift/dupes on insert; deep offsets scan & slow | Set grows or mutates → cursor |
| Idempotency keys | Safe retries; no double-charge | Server must store keys + dedupe; key TTL/scope to define | Op is naturally idempotent (PUT/DELETE) → may skip |
| URI versioning | Explicit, cache/log-visible, easy to route | Version sprawl; clients pinned forever | Changes are additive → no new version needed |
| 选项 | 适用场景 | 劣势 | 何时更换 |
|---|---|---|---|
| REST/JSON | 通用、可缓存、简单、工具生态完善 | 过度/不足获取;图谱类场景请求繁琐;类型约束弱 | 字段定制需求强烈→GraphQL;内部延迟要求高→gRPC |
| gRPC/RPC | 紧凑、类型化、快速、原生流式 | 非浏览器原生;需要代理;对HTTP缓存不友好 | 公共/浏览器客户端需要访问→REST网关 |
| GraphQL | 单次往返、客户端自定义字段 | 缓存/限流难度大;查询成本高(N+1问题);服务器复杂度高 | 字段结构固定(REST更简单)或查询成本无法控制 |
| WebSocket/SSE | 真正的服务器推送、低延迟更新 | 有状态连接,扩展/负载均衡难度大;需处理重连逻辑 | 更新频率低→长轮询;仅单向推送→SSE |
| 游标分页 | 数据插入时仍保持稳定;任意深度分页均为O(1) | 令牌不透明;无法跳转至指定页码;需要排序键 | 用户需跳转至静态数据集的第N页→偏移分页 |
| 偏移分页 | 实现简单;支持任意页码跳转 | 数据插入时会出现偏移/重复;深度偏移扫描缓慢 | 数据集增长或频繁变更→游标分页 |
| 幂等键 | 安全重试;避免重复扣费 | 服务器需存储键并去重;需定义键的TTL/范围 | 操作天然幂等(PUT/DELETE)→可省略 |
| URI版本控制 | 明确、缓存/日志可见、路由简单 | 版本泛滥;客户端长期绑定旧版本 | 变更为增量式→无需新版本 |
Behavior under stress
压力下的表现
The contract decides how badly a client amplifies an incident.
- Retry storms. A timed-out write that isn't idempotent gets retried and may
double-execute; clients retrying in lockstep stampede a recovering service.
Idempotency keys make retries safe; the backoff/jitter that paces them is owned
by . Surface
resilience-failure+429so well-behaved clients slow down.Retry-After - Deep pagination. Offset pagination at large offsets forces the store to scan and discard rows — a cheap-looking endpoint becomes a full-table scan under a crawler. Cursor pagination keeps every page O(page-size).
- Unbounded responses. No default page size, no max payload, or a GraphQL query that fans out → one request exhausts memory/CPU. Cap page size, depth, and complexity at the contract.
- Connection exhaustion. WebSocket/SSE hold a connection per client; a reconnect storm after a deploy can exhaust file descriptors and load-balancer slots. Plan reconnect with jitter and connection limits.
- Versioning breakage. A non-additive change to a shared shape breaks every client at once — the loudest self-inflicted outage. Make changes additive; deprecate behind a version.
Monitor: error-rate by status class (4xx vs 5xx), p99 latency per endpoint,
retry/idempotency-replay rate, page-depth distribution, open connection count, and
per-version traffic (to know when an old version can be retired).
契约决定了客户端在故障场景下的影响程度。
- 重试风暴。非幂等的写入操作超时后被重试,可能导致重复执行;客户端同步重试会对恢复中的服务造成冲击。幂等键让重试变得安全;控制重试节奏的退避/抖动逻辑属于范畴。返回
resilience-failure+429响应,让合规的客户端降低请求频率。Retry-After - 深度分页。偏移分页在大偏移量时会强制存储扫描并丢弃大量行——看似简单的端点在爬虫访问时会变成全表扫描。游标分页让每一页的开销均为O(页大小)。
- 无界响应。未设置默认页大小、最大负载,或GraphQL查询过度展开→单次请求耗尽内存/CPU。在契约中限制页大小、查询深度和复杂度。
- 连接耗尽。WebSocket/SSE为每个客户端保持一个连接;部署后的重连风暴可能耗尽文件描述符和负载均衡器插槽。规划带抖动的重连机制和连接限制。
- 版本兼容性破坏。对共享结构的非增量变更会立即破坏所有客户端——这是最严重的人为故障。采用增量式变更;通过版本管理废弃旧逻辑。
监控指标:按状态分类的错误率(4xx vs 5xx)、每个端点的p99延迟、重试/幂等重放率、分页深度分布、活跃连接数,以及各版本的流量(用于判断旧版本是否可退役)。
How to apply
实施步骤
- Clarify the inputs. Pin call shape, read/write ratio, result-set size, retry safety, client diversity, and latency/payload budget (see Clarify first). No contract before these are known.
- Pick the protocol and pagination from the trade-off table. Default to REST/JSON; escalate to gRPC (internal latency/typing), GraphQL (many field shapes), or a push tier (server must push) only when an input forces it. Default pagination to cursor; reserve offset for small, static, jump-to-page sets.
- Set the key knobs. Choose idempotent verbs by safety (GET/PUT/DELETE safe
to retry, POST not), define the contract and its TTL, fix a stable error envelope, and pick a versioning policy (additive by default).
Idempotency-Key - Stress-test the contract. Cap default and max page size, GraphQL
depth/complexity, and payload size; plan reconnect-with-jitter and connection
limits for push; surface +
429. Confirm a v1 client survives a v2 deploy.Retry-After - Size it with numbers. Estimate requests/s per endpoint, response size,
pages-per-session, and update-frequency × fanout; use these to confirm the
protocol and poll-vs-push choice (→ ).
back-of-the-envelope - Pick a provider. Keep the generic recipe unless the user names a cloud, then map to its managed gateway (see Choosing a provider).
- 明确输入条件。确定调用结构、读写比、结果集大小、重试安全性、客户端多样性,以及延迟/负载预算(见「先明确以下问题」)。在这些条件明确前,不要定义契约。
- 根据权衡表选择协议和分页策略。默认选择REST/JSON;仅当输入条件要求时,才升级到gRPC(内部延迟/类型约束)、GraphQL(多字段结构需求)或推送层级(服务器需主动推送)。分页默认选择游标;仅在小型、静态、需跳转页码的场景下使用偏移分页。
- 设置核心参数。根据安全性选择幂等动词(GET/PUT/DELETE可安全重试,POST不可),定义契约及其TTL,确定稳定的错误响应结构,选择版本控制策略(默认增量式)。
Idempotency-Key - 压力测试契约。限制默认和最大页大小、GraphQL查询深度/复杂度、负载大小;为推送机制规划带抖动的重连和连接限制;返回+
429响应。验证v1客户端在v2版本部署后仍能正常运行。Retry-After - 量化评估。估算每个端点的请求数/秒、响应大小、每会话分页次数,以及更新频率×客户端数量;用这些数据验证协议和轮询vs推送的选择(→ )。
back-of-the-envelope - 选择服务商。默认采用通用方案;如果用户指定了云服务商,参考中的托管服务映射、配额/限制,以及服务商特定的权衡点。如果该服务商没有对应的文档,则使用通用方案。
references/providers/<provider>.md
Dos and don'ts
注意事项
Do
- Default to REST/JSON and cursor pagination; escalate only when a number forces it.
- Choose verbs by retry safety so retries are correct by construction.
- Require an on every non-idempotent write and bound its TTL.
Idempotency-Key - Cap page size, query depth/complexity, and payload so worst-case cost is bounded.
- Keep one stable error envelope (code, message, request_id, retryable) across every endpoint.
Don't
- Reach for gRPC, GraphQL, or streaming because they sound modern (YAGNI red flag).
- Use offset pagination on growing or mutating sets — deep offsets scan the store.
- Ship a non-additive change to a shared shape without a new version.
- Leave responses unbounded (no default page size, no max payload).
- Re-teach retries/backoff or sharding here — link to /
resilience-failure.data-storage
建议
- 默认使用REST/JSON和游标分页;仅当数据指标要求时才升级方案。
- 根据重试安全性选择动词,让重试操作天然合规。
- 要求所有非幂等写入操作携带,并限制其TTL。
Idempotency-Key - 限制页大小、查询深度/复杂度和负载大小,确保最坏情况下的成本可控。
- 为所有端点保持统一的稳定错误响应结构(错误码、消息、request_id、是否可重试)。
禁止
- 因gRPC、GraphQL或流式传输听起来时髦就选用(YAGNI危险信号)。
- 在增长或频繁变更的数据集上使用偏移分页——深度偏移会扫描存储。
- 对共享结构进行非增量变更时不发布新版本。
- 允许无界响应(未设置默认页大小、最大负载)。
- 在本技能中重复讲解重试/退避或分片逻辑——链接到/
resilience-failure。data-storage
Numbers that matter
关键量化指标
Estimate before choosing: requests/s per endpoint, average and max response size,
page size × pages-per-session (drives read load), and update frequency × fanout
(decides poll vs push). A few rules of thumb that flip a decision: a binary
protocol (protobuf) commonly cuts payload several-fold over JSON, and compression
helps again before the wire — both matter most on mobile/high-latency links where
round trips dominate. Polling at interval for clients is requests/s
of mostly-empty answers; once that approaches the push tier's connection budget,
switch to SSE/WebSocket. Cap page size (and GraphQL depth/complexity) so worst-case
response cost is bounded, and keep an idempotency key's stored window bounded
(e.g. 24h) so the dedupe table doesn't grow without limit. For the canonical
latency/QPS/payload reference figures, go to — don't restate
its tables here.
TNN/Tback-of-the-envelope选择方案前先估算:每个端点的请求数/秒、平均和最大响应大小、页大小×每会话分页次数(影响读取负载),以及更新频率×客户端数量(决定轮询vs推送)。几个影响决策的经验法则:二进制协议(protobuf)通常比JSON小几倍,压缩后进一步降低传输大小——这在移动/高延迟链路中尤为重要,因为往返次数是主要延迟来源。N个客户端以间隔T轮询,会产生N/T次请求/秒,且大多为空响应;当这个数值接近推送层级的连接预算时,切换到SSE/WebSocket。限制页大小(和GraphQL查询深度/复杂度),确保最坏情况下的响应成本可控;限制幂等键的存储窗口(如24小时),避免去重表无限增长。关于延迟/QPS/负载的标准参考数据,详见——不要在此重复其表格内容。
back-of-the-envelopeInterface sketch
接口示例
Make the contract concrete. A read with cursor pagination and an error 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 }让契约具体化。以下是带游标分页和错误响应结构的读取接口:
GET /v1/users/{id}/posts?limit=20&cursor=eyJ0cyI6MTciLCJpZCI6Ijk5In0
200 OK
{ "data": [ { "id": "p_881", "created_at": "...", "text": "..." } ],
"next_cursor": "eyJ0cyI6...", // 无更多数据时为null
"has_more": true }error envelope — stable shape across every endpoint
错误响应结构——所有端点保持统一
4xx/5xx
{ "error": { "code": "rate_limited", "message": "…", "request_id": "req_…",
"retryable": true } }
An idempotent write (this skill's owned contract):
POST /v1/payments
Idempotency-Key: 9f1c-… (client-generated UUID, unique per logical operation)
{ "amount": 4200, "currency": "usd", "source": "card_…" }
4xx/5xx
{ "error": { "code": "rate_limited", "message": "…", "request_id": "req_…",
"retryable": true } }
以下是幂等写入接口(本技能负责的契约):
POST /v1/payments
Idempotency-Key: 9f1c-… (客户端生成的UUID,每个逻辑操作唯一)
{ "amount": 4200, "currency": "usd", "source": "card_…" }
Server: first request with a key → execute, store (key → response) for a TTL.
服务器逻辑:首次使用该键的请求→执行操作,存储(键→响应)并设置TTL。
Retry with same key → return the stored response, do NOT re-execute.
使用同一键重试→返回存储的响应,不重新执行。
Same key + different body → 422 (key reuse conflict).
同一键+不同请求体→返回422(键复用冲突)。
Mirror the response shape to the access pattern: cursor encodes the partition/sort
key the store pages on (PK/SK design is owned by `data-storage`). Pick verbs by
safety — GET (safe), PUT/DELETE (idempotent), POST (not), so retries are correct
by construction.
响应结构需匹配访问模式:游标编码了存储分页使用的分区/排序键(PK/SK设计属于`data-storage`范畴)。根据安全性选择动词——GET(安全)、PUT/DELETE(幂等)、POST(非幂等),让重试操作天然合规。Choosing a provider
选择服务商
Default to the generic recipe above. If the user names a cloud, read
for the managed-service mapping,
quotas/limits, and provider-specific trade-offs. If no file exists for that
provider, the generic recipe is the answer.
references/providers/<provider>.md默认采用上述通用方案。如果用户指定了云服务商,参考中的托管服务映射、影响决策的限制,以及各环境的陷阱。如果该服务商没有对应的文档,则使用通用方案。
references/providers/<provider>.mdDiagram
图表可视化
To visualize the request/response path, the cursor-paging loop, or an
idempotent-retry sequence (client → gateway → service → store, with the replay
branch), use the in-plugin skill — do not embed Mermaid
here. A one-line ASCII sketch inline is fine for quick reasoning.
architecture-diagram如需可视化请求/响应路径、游标分页循环或幂等重试流程(客户端→网关→服务→存储,含重放分支),使用插件内的技能——不要在此嵌入Mermaid代码。可使用单行ASCII草图进行快速说明。
architecture-diagramRelated building blocks
相关组件
- — depends on it: the request/response and cursor shapes mirror its primary key and access patterns (sharding/partitioning lives there); design them together.
data-storage - — pairs with it: it owns retries/backoff/jitter, timeouts, and rate limiting, while idempotency keys (owned here) are what make those retries safe.
resilience-failure - — pairs with it when a retried or concurrent write must not violate an invariant; CAP/quorum theory lives there.
consistency-coordination - — alternative to synchronous request/response when the contract is async events or webhooks.
messaging-streaming - — feeds into this: the orchestrator routes here at the interface step.
system-design
- —— 依赖:请求/响应和游标结构需匹配其主键和访问模式(分片/分区设计属于该范畴);需协同设计。
data-storage - —— 配套:该组件负责重试/退避/抖动、超时和限流,而本技能负责的幂等键是这些重试操作安全的保障。
resilience-failure - —— 配套:当重试或并发写入不能违反约束时使用;CAP/一致性理论属于该范畴。
consistency-coordination - —— 替代方案:当契约为异步事件或Webhooks时,替代同步请求/响应。
messaging-streaming - —— 前置:架构设计流程在接口阶段会导向本技能。
system-design
References
参考资料
- — protocol mechanics (HTTP verbs/status discipline, gRPC streaming modes, GraphQL query-cost limits), cursor-token construction, the full idempotency-key state machine, versioning/deprecation strategy, and error-contract design. Read when designing the contract in detail.
references/deep-dive.md - — API-gateway / managed-endpoint mappings, limits that change a decision, and pitfalls per environment.
references/providers/{generic,aws,azure,gcp}.md
- —— 协议机制(HTTP动词/状态规范、gRPC流式模式、GraphQL查询成本限制)、游标令牌构造、完整的幂等键状态机、版本控制/废弃策略,以及错误契约设计。详细设计契约时阅读。
references/deep-dive.md - —— API网关/托管端点映射、影响决策的限制,以及各环境的陷阱。
references/providers/{generic,aws,azure,gcp}.md