跳到正文
册页

图示

ArchitectureFigure - 架构图

一张组件地图:服务、数据存储、信任边界,以及谁在跟谁说话。

用法

什么时候用它

问题是“谁跟谁说话”时用它。问题是“按什么顺序”,那是流程图或时序图;问题是“这根箭头里装的是什么”,那是数据流图。

来自@misoto22/folio/diagrams

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

说明

一张组件地图:服务、数据存储、边界,以及谁在跟谁说话。

它吃的是 archify architecture 规格所携带的同一份 JSON——components、boundaries、connections——然后用这套系统自己的语汇画出来:细线上的纸质板块、给这张图真正要讲的那个组件一块反白板,以及七个画出来的符号,替掉 archify 用的七种色相。

它能在服务端渲染,因为每一个位置本来就写在规格里。一个组件要么给出 row 和 col——落进一个格子尺寸要么声明、要么取默认的网格——要么给出绝对的 pos。这里没有任何东西是解出来的、松弛出来的或量出来的。所以标记是输入的纯函数,服务端和浏览器完全一致,水合时也不会有布局抖动——因为压根没有布局要做。

两样都不声明的组件会顺着流走:它按声明顺序取下一个空格子,到 layout.cols 处换行。这依然是在规格自己带着的数字上做算术,而不是一个求解器——而另一条路是:每个没摆放的组件都默认落到第 0 行第 0 列,叠成同一块板。

边界先画,而且画得不一样。region 是某个东西运行在哪里——一个 VPC、一个可用区、一个集群——画成实线框。security-group 是什么够得到它,画成虚线。这不是装饰:一张基础设施图非常经常只被拿来回答这两个问题中的一个,而读者应该在不读任何一个标签的情况下就能分辨哪根线回答哪个问题。

结构

ArchitectureFigure anatomy
部件说明
图外框必填五张图共用的那个框——衬线标题、可横滚的纸面、role="img" 的 svg。地图交给它的是那份关系列表:每一条连接都发布成「CloudFront → API:HTTPS」,而这正是这一类图存在的意义所在的那句话。
网格必填row 和 col 索引进去的东西:184 × 72 的单元格,横竖间距分别是 64 和 76 个单位,可以按图用 spec.layout 覆盖——其中 cellW 既是步距,也是按这个步距画出来的那些板块的宽度。row 和 col 都不声明的组件会流进下一个空位,到 layout.cols 处折行;pos 则把整个网格一起顶掉,改为把方框放在一个绝对坐标上。
组件板块必填一个组件一个方框:首行是符号和眉标,名字用可读的字号,下面是等宽的副标签。有 tag 时眉标印组件自己的 tag,没有就印种类词;方框会长过声明的高度,而不是从自己的底边线里印出去。
边界框一圈带标签的线,围住 wraps 点名的那些方框的并集,再按 pad 外扩——默认 28 个单位。region 是实线,security-group 是虚线,所以「东西跑在哪」和「谁够得着它」在读到任何一个标签之前,就已经是两种不同的线了。
连接垂直于某一面出发、也垂直于某一面到达的线;同一面上有好几条时,它们会沿着这一面铺开。每条线的文字都落在一块遮罩上,把线从自己底下挖掉。
图例真正画出来的那些种类,每一种都配上板块本身带的那个符号。legend="all" 改为把七种全列出来;legend="hidden" 一种都不印。

实践建议

推荐

  • 有变化就造一个新的 spec 对象。模型是按 spec 的引用身份做记忆化的,所以就地改 spec.components 留下的还是同一个引用,图会一直画着它最初拿到的那一张——开发构建抓到时会打印 DIAGRAM_SPEC_MUTATED,生产构建什么也不打印,照旧画那张旧的。
  • 一条连接带了手调的 via、channelX 或 channelY 时,两端都要用 pos 摆。这几项只有在两个端点都是绝对定位时才作数——在网格上,它们是一套渲染器并没有选定的空间里的坐标,所以两个用 row/col 摆的组件之间的路径会被丢掉重新布线。
  • 引导式阅读要你自己驱动。meta.views 类型能过,却没有任何渲染器去读它:章节的 focus id 必须以 activeIds 的形式送进来,那才是把其余部分压暗、并加上「n highlighted」那行状态的东西。
  • 没有主张的组件,row 和 col 就都别写。它们会按声明顺序流进下一个空位,到 layout.cols 处折行,并绕开那些摆好的组件已经占掉的格子——所以不写位置,是把位置交给布局,而不是把这些板块全叠到同一个格子上。

避免

  • 同一个格子上的两个组件,仍然是同一个坐标上的两块板块,一块盖着另一块:第二块板块没有第二个地方可去。开发构建会打印 DIAGRAM_CELL_COLLISION 并点出这两个,而这是唯一的信号——画出来的东西,有没有这条警告都长得一模一样。
  • 边界会不声不响地缩到它找得到的那些组件上。wraps 里没有任何组件声明的 id 会被跳过,而 id 全都对不上的边界干脆不画框——于是一条信任边界可以从画面上消失,而规格里还写着它。

示例

default

一条请求路径,画在默认栅格上:每个组件都指名了自己的行和列,所以这里没有任何东西是被算出来的,服务端发出的标记就是浏览器留下的标记。区域边界正是让 ap-southeast-2 成为一个地方、而不是第六个盒子的东西,也正是它把浏览器明确地放在了外面。有一条连接带着强调——没有它,四条等分量的箭头会让读者自己去琢磨这张图到底在讲哪一条。

One request, end to end

The edge, the service, the row it reads.
One request, end to end5 elements and 4 relationships. The edge, the service, the row it reads.EXTERNALBrowserSafari / ChromeCLOUDCloudFrontCDN:8000APIFastAPIDATARedisread-throughDATAPostgresprimary

5 elements and 4 relationships. The edge, the service, the row it reads.

  • Browser (external) — Safari / Chrome
  • CloudFront (cloud) — CDN
  • API (backend) — FastAPI
  • Redis (database) — read-through
  • Postgres (database) — primary
  • Browser → CloudFront: HTTPS
  • CloudFront → API
  • API → Redis: read-through
  • API → Postgres: SQL
  • Region ap-southeast-2 encloses API, Redis, Postgres.
Key
  • EXTERNAL
  • CLOUD
  • SERVICE
  • DATA
Edge
  • Every request is fronted by the CDN.
Application
  • Reads go through Redis first.
  • Postgres is the source of truth.

two kinds of boundary

两种边界,一种嵌在另一种里面。region 是围在「某样东西运行的地方」外面的实线框,而 security-group 是围在「什么可以够到它」外面的虚线框,所以一个带着这两个问题之一而来的读者,不用读任何一条说明文字,就分得出哪个框回答的是他的问题。security 那个变体标出了唯一那一跳跨过虚线的连接,而它里面从队列到 worker 的箭头完全没有配文字——只有当恰好只有一条箭头离开那个盒子时这才负担得起,一旦有第二条,它就成了猜测。

An inbound webhook

Where it runs, and what is allowed to reach it.
An inbound webhook4 elements and 3 relationships. Where it runs, and what is allowed to reach it.EXTERNALPartnerwebhook sendermTLSGatewaysignature checkQUEUEIngest queueSQSSERVICEWorkerconsumer

4 elements and 3 relationships. Where it runs, and what is allowed to reach it.

  • Partner (external) — webhook sender
  • Gateway (security) — signature check
  • Ingest queue (messagebus) — SQS
  • Worker (backend) — consumer
  • Partner → Gateway: signed POST
  • Gateway → Ingest queue: verified
  • Ingest queue → Worker
  • Region ap-southeast-2 encloses Gateway, Ingest queue, Worker.
  • Security group Reachable only from the gateway encloses Ingest queue, Worker.
Key
  • EXTERNAL
  • POLICY
  • QUEUE
  • SERVICE

a denser map

八个盒子、八条线,画在一张通过布局收紧过的栅格上,而这既是一个真实系统的样子,也是密度的代价:到了这个数量,没人会去追一条路径,他们是在扫着找一条。那两条被强调的连接和下面那些卡片就是补救——更重的那一段说明从哪儿看起,而那些卡片把结论直接陈述出来,好让读者不必自己去推。一张这个尺寸、每条线都一个分量的图,是一堵在邀请读者去解题的墙。

The platform

Eight services, and one path through them.
The platform8 elements and 8 relationships. Eight services, and one path through them.CLIENTWebNext.jsCLIENTMobileiOSSERVICEGatewayroutingPOLICYAuthOIDCSERVICEOrdersserviceSERVICESearchserviceDATAPostgresprimaryDATAOpenSearchindex

8 elements and 8 relationships. Eight services, and one path through them.

  • Web (frontend) — Next.js
  • Mobile (frontend) — iOS
  • Gateway (backend) — routing
  • Auth (security) — OIDC
  • Orders (backend) — service
  • Search (backend) — service
  • Postgres (database) — primary
  • OpenSearch (database) — index
  • Web → Gateway
  • Mobile → Gateway
  • Gateway → Auth: verify
  • Gateway → Orders: REST
  • Gateway → Search: REST
  • Orders → Postgres: SQL
  • Search → OpenSearch: query
  • Orders → OpenSearch: reindex
Key
  • CLIENT
  • SERVICE
  • POLICY
  • DATA
Checkout
  • Gateway to Orders to Postgres carries every paid order.
Coupling
  • Orders writes the index Search reads.
  • Only Orders and Search reach a datastore at all.

placed by hand

用 pos 而不是 row 和 col,而这正是解锁一条手工调过的走线的东西:via、channelX 和 channelY 只有在这条连接的两端都是绝对定位时才会被采纳。在两个栅格组件之间,它们是渲染器自己选的坐标系里的坐标、而不是作者选的,所以它们会被丢掉,线路一声不吭地重新布置。这里也没有任何东西检测碰撞——两个被给了同一个坐标的组件,会一块板压着另一块板画出来,图形照样渲染,而旁边那份摘要列表仍然把两个都报出来。

Orders, with the retry drawn under the row

Every box placed by hand, so the loop can be too.
Orders, with the retry drawn under the row4 elements and 4 relationships. Every box placed by hand, so the loop can be too.SERVICECheckout APIFastifyQUEUESQS ordersstandard queueSERVICEFulfilment workerECS serviceQUEUEorders-dlqpaged on depth > 0

4 elements and 4 relationships. Every box placed by hand, so the loop can be too.

  • Checkout API (backend) — Fastify
  • SQS orders (messagebus) — standard queue
  • Fulfilment worker (backend) — ECS service
  • orders-dlq (messagebus) — paged on depth > 0
  • Checkout API → SQS orders: publish
  • SQS orders → Fulfilment worker: consume
  • Fulfilment worker → orders-dlq: after 5 attempts
  • Fulfilment worker → SQS orders: retry in 30s
Key
  • SERVICE
  • QUEUE

无障碍

  • <svg> 是 role="img" 并带名字,所以读屏软件播报的是一张图,而不是按绘制顺序把两百个 <text> 节点走一遍。
  • 图的内容以普通列表的形式发布在图旁边——每个节点连同它的种类,每条关系写成「A → B:走 HTTPS」。对看不到这张图的人来说,意思就活在那份列表里。
  • 传了 onSelectNode,那份列表就变成真按钮——这是键盘唯一能选中节点的路径:图里的板块按设计就是纯装饰的。