API 流程图指南

API 流程图是什么、要画出哪些东西才值得画,以及三个可以直接改的例子:一次 REST 请求、超时后的重试、异步回调。

发布于 ·9 分钟阅读
apisequence-diagramresttemplate

一、什么是 API 流程图

API 流程图 画的是「客户端发出请求」到「拿到响应」之间真正发生了什么:哪些服务参与了、什么顺序、各自返回什么。这是 API 文档给不了你的那张图 —— 文档是一个接口一个接口写的,而一个接口几乎从来不是故事的全部。

这件事的正确画法是 时序图,不是流程图。理由是结构性的:一次 API 调用是好几方之间的对话(客户端、网关、认证服务、你的服务、数据库、第三方),而有价值的信息是 谁跟谁说话、什么顺序。时序图给每一方一条竖着的生命线、时间朝下走,顺序就是这张图的形状。流程图只有一个隐含的执行者、没有时间轴,一旦有三个服务它就开始骗人。

API 流程图真正值钱的地方是 异常路径。成功路径通常一目了然,大家本来就没有分歧。有分歧的是:401 到底由谁返回 —— 网关还是认证服务?支付方超时了要不要重试?409 是谁的责任?这些分歧写在文档里看不出来,画成图就藏不住,因为一条没有标注的箭头就是一个可以指着问的洞。

一个 流程 一张图,不是一个接口一张图。「创建订单」是一个流程,POST /v1/orders 是它里面的一条箭头。

二、一张 API 流程图要画出哪些东西

只画成功路径的图是装饰品。有这 5 样东西,它才承重:

  • 所有参与方,包括不起眼的那些。 网关、缓存、数据库是最常被省掉的三个,而延迟和故障恰恰住在那里。
  • 箭头上写方法和路径,不要只写「请求」。 POST /v1/orders 能让读者知道该去翻哪段代码,「创建订单」不能。
  • 返回箭头上写状态码。 这是性价比最高的一个细节。201 还是 200 还是 202 是一个真实的设计决策,写在箭头上能逼团队一次性定下来,而不是在三个地方各定一次。
  • 至少一条失败分支。alt 块。如果你说不出这个流程会怎么失败,说明你还没有认真看过它。
  • 每次调用是不是同步的。 「等回复的调用」和「扔进队列就不管」在文字里长得一模一样,在图里完全不同。

这是满足前三条的最小的一张图。下面每个例子都是从这个骨架长出来的 —— 四个参与方,路径写明,状态码写明。

看 Mermaid 源码
sequenceDiagram
    participant Client
    participant Gateway as API Gateway
    participant Service as Orders Service
    participant DB as Database

    Client->>Gateway: POST /v1/orders
    Gateway->>Service: forward with user id
    Service->>DB: insert order row
    DB-->>Service: order id
    Service-->>Gateway: 201 Created
    Gateway-->>Client: 201 Created + Location
最小的 API 流程图:客户端向网关发起请求,网关转发给订单服务,订单服务写库后返回 201 Created。

三、API 流程图怎么画

三步,按顺序来。上来就画箭头的人最后都要重画。

第 1 步 · 列出参与方,然后砍掉一些

先把请求碰到的一切写下来。然后删掉那些从不自己发消息的参与方 —— 如果一个组件只是夹在两者中间原样转发,那它是基础设施,不是参与方,画出来只多一条生命线、不多一点信息。

4 到 6 个参与方最合适。超过 7 个,图就比屏幕宽了,人就不看了。如果确实有 9 个,那是个信号:把流程拆开 ——「客户端 → 网关 → 服务」画一张,「服务 → 下游扇出」画另一张,互相链接。

第 2 步 · 先画通路,再故意把它弄坏

先把成功路径画完 —— 通常 5 到 8 条箭头,两分钟的事。然后回头对每一条箭头问同样三个问题:

这一步超时了会怎样? 不是「返回错误会怎样」—— 超时更糟,因为你不知道对面到底做没做。这个区别就是下面第二个例子的全部内容。

这一步返回 4xx 会怎样? 相邻两方里哪一个负责翻译它、翻译成什么?下游一个 404 冒到客户端变成 500,是最常见的 API bug 之一,而只要把两条箭头都画出来,它立刻就现形了。

这一步能安全重试吗? 能的话就写在箭头上。不能的话,图里得画出是什么让它变安全的 —— 幂等键、去重表,还是一次状态检查。

每一个「是,这个会发生」都变成一个 alt 块。三四个 alt 块是一张健康的图;零个说明你画的是宣传册版本。

第 3 步 · 画出来

把这次调用当普通句子描述出来,让 text2diagram 排版 ——「客户端向网关发请求,网关拿 token 去认证服务校验,通过后转给订单服务,订单服务写 Postgres 然后返回 201;token 过期的话网关直接返回 401」,还回来的是一段可以直接改的 sequenceDiagram

更快的办法:把下面任何一个例子在编辑器里打开,然后把参与方改成你自己的。费脑子的是箭头的排布方式,名字是三十秒就能重打一遍的部分。

四、三个 API 流程图例子

三个流程,覆盖了真实 API 做的大部分事:一次带鉴权的同步请求、一次以最糟糕方式失败的调用,以及一个慢到不能当场回答的操作。

例 1 · 一次完整的 REST 请求

这里值得抄的不是形状,而是 两条 失败分支都画出来了,而且每条都点名了谁产出那个状态码。401 由网关返回(认证服务只说「不通过」);409 由订单服务返回(数据库只说「唯一键冲突」)。把这个写下来,能了结一场否则每隔几个月就要重打一次的架。

箭头超过八条的图值得开 autonumber —— 它给评审的人一个可以指着说的东西。

看 Mermaid 源码
sequenceDiagram
    autonumber
    participant Client
    participant Gateway as API Gateway
    participant Auth as Auth Service
    participant Orders as Orders Service
    participant DB as Postgres

    Client->>Gateway: POST /v1/orders (Bearer token)
    Gateway->>Auth: verify token
    alt token expired or invalid
        Auth-->>Gateway: rejected
        Gateway-->>Client: 401 Unauthorized
    else token valid
        Auth-->>Gateway: user id and scopes
        Gateway->>Orders: POST /orders
        Orders->>DB: insert order row
        alt unique constraint hit
            DB-->>Orders: duplicate key
            Orders-->>Gateway: 409 Conflict
            Gateway-->>Client: 409 Conflict
        else row written
            DB-->>Orders: order id
            Orders-->>Gateway: 201 Created
            Gateway-->>Client: 201 Created + Location
        end
    end
一张 REST API 流程图:客户端、网关、认证服务、订单服务和 Postgres,401 和 409 两条失败分支都用 alt 块画出。

例 2 · 超时,以及为什么不能直接重试

这是最值得贴在墙上的一张。超时不是错误 —— 错误告诉你「事情没发生」,超时什么也没告诉你。虚线 --x 是 Mermaid 里「消息没送到」的画法,它在这里真的在干活:它把「对方说不行」和「我们完全不知道」在视觉上分开了。

解法是它后面那两条箭头:先查,再重试。先按幂等键查一次,查不到才重试。跳过这次查询的团队会给客户重复扣款,而之所以会跳过,几乎总是因为没人画过这张图。

看 Mermaid 源码
sequenceDiagram
    autonumber
    participant Client
    participant API as Your API
    participant Provider as Payment Provider

    Client->>API: POST /payments (Idempotency-Key abc)
    API->>Provider: charge card
    Provider--xAPI: timeout after 10s
    Note over API: outcome unknown - a blind retry may double charge
    API->>Provider: GET /charges by idempotency key
    alt charge already exists
        Provider-->>API: charge succeeded
        API-->>Client: 200 OK
    else nothing was charged
        API->>Provider: retry with the same key
        Provider-->>API: charge succeeded
        API-->>Client: 200 OK
    end
一张 API 流程图,展示支付方超时后先按幂等键查询、再有条件重试。

例 3 · 异步操作 + 回调

当一件事慢到不该占着一次请求时,API 就不再是一问一答,而变成两段独立的对话。图必须把这个接缝画出来:202 Accepted 是在任何实际工作发生 之前 就返回的,它之后的一切都是另一个流程,而且调用方管不着。

有两个细节常被省掉、然后后悔。一是重试节奏应该画进图里(那条 Note 不是装饰 ——「客户的回调我们重试几次」是客服一定会问的问题)。二是客户的回调地址和别的参与方一样是个参与方,也就是说它同样需要失败分支。它是这张图上最不可靠的一个框,却最经常被画成永远返回 200。

看 Mermaid 源码
sequenceDiagram
    autonumber
    participant Client
    participant API as Your API
    participant Queue
    participant Worker
    participant Hook as Customer Webhook

    Client->>API: POST /exports
    API->>Queue: enqueue job
    API-->>Client: 202 Accepted + job id
    Queue->>Worker: deliver job
    Worker->>Worker: build the export file
    Worker->>Hook: POST /webhooks/export-ready
    alt endpoint returns 2xx
        Hook-->>Worker: 200 OK
    else endpoint errors or times out
        Hook-->>Worker: 500
        Worker->>Queue: requeue with backoff
        Note over Queue,Worker: 5 attempts - 1m, 5m, 30m, 2h, 12h
    end
一张异步 API 流程图:任务入队后立刻返回 202 Accepted,worker 生成文件后回调客户地址,失败按退避策略重试。

图放在 handler 旁边,不要放 wiki

API 流程图比几乎任何其他图都更容易过期,因为流程每个迭代都在变,而 wiki 页面不会跟着变。这里的源码就是一段 Mermaid 纯文本,把它提交到仓库里、放在拥有这个流程的代码旁边 —— GitHub 原生渲染 Markdown 里的 Mermaid 代码块,所以图会出现在 README 里,也会在 code review 里以 diff 的形式出现。只有和行为在同一个 PR 里一起改的图,才会一直是真的。

常见问题

接着读

去试试 text2diagram

打开工具
← 回到全部教程