跳到正文
册页

图示

SequenceFigure - 时序图

一条随时间展开的调用链:谁问谁、按什么顺序、又回来了什么。

用法

什么时候用它

唯一一张纵轴有含义的图。消息带显式的 y,所以相隔八个单位的两次调用是同时发生的,相隔两百个单位的不是。

来自@misoto22/folio/diagrams

TSX
import { SequenceFigure } from '@misoto22/folio/diagrams'

说明

一条随时间展开的调用链:谁问谁、按什么顺序、又回来了什么。

这是这里唯一一张纵轴有含义的图。其他每张图的 y 都是布局;时序图的 y 是时间——所以消息带的是显式的 y 而不是序号:相隔 8 个单位的两次调用是同时发生的,相隔 200 个单位的不是;把消息等距排开会一边显得更整齐、一边把这个区别抹掉。

生命线是细线而不是实线,因为它们是坐标轴而不是内容。一条画得跟消息一样重的生命线,会让七条竖线和十二条横线互相抢戏。

激活条说的是谁正忙着,这正是一张时序图能承载、而一份调用清单承载不了的事实——第三个参与者的条和第二个重叠,才是画它们的理由。

区段把纵轴分成几个阶段——请求、回退、响应——印成一条线加一行等宽说明,而不是一块着色的面板,理由和架构图的边界一样:图里的第二层地面会压在每一条消息标签的遮罩下面。

return 消息既是虚线又用空心箭头。两个信号而不是一个,因为在一条密集的调用链里,回包是读者最常需要挑出来的东西,而单靠虚线,做的已经是 dashed 在异步调用上做过的那份工作。

结构

SequenceFigure anatomy
部件说明
图外框必填画面外面的那个框。一条调用链交给它的是一份消息列表,而这份列表是按 messages 数组书写的顺序发布的——那也是看不见纵轴的读者能拿到的唯一顺序。
参与者头必填每个参与者在自己那一列顶上有一块 148 单位宽的板块,顺序由 participants 数组给定。meta.column_fit: "spread" 会把每一列都撑到最宽的那个名字,而不是守着固定宽度。
生命线必填从每个头往下拉到轴上最后一样东西下方的一根细线。是细线而不是普通线,因为它就是那根轴:七条按消息线重画的生命线,就是七条竖线在跟十二条横线抢。
消息必填在自己的 y 上的一条横线,两端都从生命线往里缩,让箭头落在轴之外,文字压在它上方的一块遮罩上。返回消息既是虚线、又用空心箭头——两个信号,因为读者要从一条密集的调用链里挑出来的正是回包。
激活条生命线上从一个 y 到另一个 y 的一根窄条:谁正忙着。这是时序图承载、而一份调用清单承载不了的事实;两根条彼此重叠,正是这张图值得画出来的全部理由。
分段带一条横贯全图的虚线,配一个落在遮罩块上的等宽小标题——request、fallback、response。是一个小标题而不是一块着色面板,因为图里再来一层底色,就会压在每一条消息标签的遮罩底下。

实践建议

推荐

  • 消息要按 y 从小到大写。画面读的是纵轴,而旁边那份摘要列表遍历的是数组,所以一条顺序写错的消息,画在这次交互的一个位置上,念出来却在另一个位置。
  • 参与者的名字比固定列宽还长时,就用 meta.column_fit: "spread"。板块会折成两行,仍然放不下的部分打省略号;而把名字改短来迁就宽度不是修复——名字就是数据本身。
  • 消息之间的间距要按实际发生的情况给。没有任何东西会把 y 归一化,所以两次调用相隔八个单位读作同时发生、相隔两百个单位读作一段等待;均匀排开的列表是整齐些,但它说出了这条调用链没说的事。

避免

  • 点名了一个 participants 没有声明的参与者的消息,会连同摘要列表一起消失——没有两列可以让它画在中间,而一份文字等价物若报告了画面根本展示不了的调用,就是同一张图的两半在互相打架。开发环境会打印 DIAGRAM_EDGE_DANGLING 并带上两端,那也是这条消息唯一被报出来的地方。
  • 这张图是在断言顺序。一个服务并发发出的三次调用,因为总得给个 y 而被给了三个 y,画出来就是在声称第二次等了第一次——「这几件事是重叠的」该说的地方,是激活条或者分段小标题。

示例

default

一次缓存未命中,顺着页面往下读,而竖轴是时间、不是布局:未命中和查询之间那 50 个单位是一段真实的间隔,而一份六次调用等距排开的列表,会把它抹掉,同时看起来还更整齐。那些激活条承载的是一份调用列表承载不了的事实——在 API 保持打开的那 210 个单位里,Redis 忙了 45 个单位。返回用虚线、配空心箭头,两个信号而不是一个,因为在一条密集的调用轨迹里,读者最先挑出来的就是回复。

A cache miss

The vertical axis is time, not layout.
A cache miss4 elements and 6 relationships. The vertical axis is time, not layout.CLIENTWeb appReactSERVICEAPIhandlerDATARedisDATAPostgres

4 elements and 6 relationships. The vertical axis is time, not layout.

  • Web app (frontend) — React
  • API (backend) — handler
  • Redis (database)
  • Postgres (database)
  • Web app → API: GET /me
  • API → Redis: read
  • Redis → API: miss
  • API → Postgres: select
  • Postgres → API: rows
  • API → Web app: 200 JSON
  • Phase Request covers GET /me, read, miss.
  • Phase Fallback covers select, rows, 200 JSON.
  • API is busy from GET /me to 200 JSON.
  • Redis is busy from read to miss.
  • Postgres is busy from select to rows.
Key
  • Primary path
  • Call
  • Return

an asynchronous hop

一次投递中异步的那一半。入队是虚线,因为 Billing 不等它——Billing 压根没有激活条,而正是这一点说明随后那两次尝试对调用方不花任何代价。两次对外调用都带着 security 变体,图例把它读作跨越信任边界,而在这里这是真的:那个端点属于客户。column_fit 用的是 spread,因为「Partner webhook endpoint」装不进固定的 148 单位列宽,而为了塞得下去把名字缩短,那会是另一张图了。

Webhook delivery

A queue absorbs the retry the caller never waits for.
Webhook delivery3 elements and 5 relationships. A queue absorbs the retry the caller never waits for.SERVICEBillinginvoice writerQUEUEOutboxdelivery queueEXTERNALPartner webhook endpointcustomer-operated

3 elements and 5 relationships. A queue absorbs the retry the caller never waits for.

  • Billing (backend) — invoice writer
  • Outbox (messagebus) — delivery queue
  • Partner webhook endpoint (external) — customer-operated
  • Billing → Outbox: enqueue
  • Outbox → Partner webhook endpoint: POST /hooks
  • Partner webhook endpoint → Outbox: 503
  • Outbox → Partner webhook endpoint: attempt 2
  • Partner webhook endpoint → Outbox: 200
  • Phase Retry covers attempt 2, 200.
  • Outbox is busy from enqueue to 200.
Key
  • Asynchronous
  • Crosses a trust boundary
  • Return

time as the axis

一个显式的 y 买到了什么。前三次调用相隔 12 个单位,因为它们是一起离开网关的;而供应商的回复在它那次调用下方 186 个单位处,因为它就是花了这么久——换成按下标摆放,这六条消息会等距排开,那个差别也就没了。那些激活条用第二种方式承载同一个事实:两条早早收尾,而第三条一路跑到图的底部,那就是长尾,在任何人读到一个标签之前就已经看见了。

A slow dependency

Three calls in 24 units, then 186 of waiting.
A slow dependency4 elements and 6 relationships. Three calls in 24 units, then 186 of waiting.SERVICEGatewaySERVICESearchSERVICERankerEXTERNALPricing vendor

4 elements and 6 relationships. Three calls in 24 units, then 186 of waiting.

  • Gateway (backend)
  • Search (backend)
  • Ranker (backend)
  • Pricing vendor (external)
  • Gateway → Search: query
  • Gateway → Ranker: features
  • Gateway → Pricing vendor: quote
  • Search → Gateway: hits
  • Ranker → Gateway: scores
  • Pricing vendor → Gateway: price
  • Gateway is busy from query to price.
  • Search is busy from features to hits.
  • Ranker is busy from quote to scores.
  • Pricing vendor is busy from hits to price.
Key
  • Call
  • Primary path
  • Return
The tail
  • Search and Ranker both answer within 75 units of the first call.
  • The vendor holds its request open for 186.

names that do not fit

column_fit 用 spread,它会把每一列都撑到最宽的那个参与者名字,而不是守着那个固定值。只要一个真实的名字会被裁掉,就立刻去拿它:板子会折成两行、其余部分打省略号,而为了塞得下去把名字缩短不是一种修复——名字就是数据,dealer-portal-api 和 api 是两个不同的服务。一条点名了 participants 里没有声明的参与者的消息,根本不会被画出来,因为没有列可以让它画在中间;它会以原始 id 的形式留在摘要列表里,于是文字报出了一次画面没有展示的调用。

Signing in to the dealer portal

Every column as wide as the longest real name.
Signing in to the dealer portal3 elements and 4 relationships. Every column as wide as the longest real name.CLIENTDealer portal (browser)Next.jsSERVICEdealer-portal-apiDjango + DRFDATASQL Server 2025legacy schema

3 elements and 4 relationships. Every column as wide as the longest real name.

  • Dealer portal (browser) (frontend) — Next.js
  • dealer-portal-api (backend) — Django + DRF
  • SQL Server 2025 (database) — legacy schema
  • Dealer portal (browser) → dealer-portal-api: POST /api/v1/token/
  • dealer-portal-api → SQL Server 2025: SELECT dealer WHERE email = ?
  • SQL Server 2025 → dealer-portal-api: one row
  • dealer-portal-api → Dealer portal (browser): access + refresh
  • dealer-portal-api is busy from SELECT dealer WHERE email = ? to access + refresh.
  • SQL Server 2025 is busy from SELECT dealer WHERE email = ? to one row.
Key
  • Primary path
  • Call
  • Return

无障碍

  • 返回消息既是虚线、又用空心箭头——两个信号,因为在一条密集的调用链里,读者最常要挑出来的就是回包。
  • 图旁边的消息列表按顺序读,那就是纵轴的绘制顺序。