# Software Architect:思维、语言与实践

一门面向 senior engineer 的完整课程,帮你像架构师一样思考和表达:trade-offs、boundary、数据、分布式系统、质量、沟通,以及 AI-native 的工作方式。共 52 个概念——每个都有英文术语、一张图和一张卡片(是什么 → 例子 → 代价 → 关联);结尾附一份涵盖所有概念的速查表。

- **Author:** Fedor Chishchin
- **Level:** advanced
- **Reading time:** 约 7 小时阅读
- **Published:** 2026-05-23
- **Source:** https://fedorthinks.com/zh/learn/software-architect

---

#### 详细模块

- [01. 架构师是谁](./software-architect-01-role)
- [02. Trade-offs](./software-architect-02-tradeoffs)
- [03. 技术债](./software-architect-03-technical-debt)
- [04. 认知负荷](./software-architect-04-cognitive-load)
- [05. 拆分](./software-architect-05-decomposition)
- [06. 分层与依赖](./software-architect-06-layers)
- [07. API 与 contract](./software-architect-07-api-contracts)
- [08. 数据模型](./software-architect-08-data-model)
- [09. 状态](./software-architect-09-state)
- [10. 分布式系统](./software-architect-10-distributed-systems)
- [11. 单体与微服务](./software-architect-11-monolith-microservices)
- [12. 同步与异步](./software-architect-12-sync-async)
- [13. 性能](./software-architect-13-performance)
- [14. 可扩展性](./software-architect-14-scalability)
- [15. 可靠性](./software-architect-15-reliability)
- [16. 安全](./software-architect-16-security)
- [17. Observability](./software-architect-17-observability)
- [18. 文档](./software-architect-18-documentation)
- [19. 沟通](./software-architect-19-communication)
- [20. 无职权的领导力](./software-architect-20-leadership)
- [21. 业务与产品](./software-architect-21-business-product)
- [22. AI-native 架构师](./software-architect-22-ai-native)
- [23. 反模式](./software-architect-23-antipatterns)
- [24. 持续成长](./software-architect-24-growth)

## 1. 架构师是谁

架构师和 senior engineer 的区别,不在于他懂更多技术。他为"决策在时间维度上的后果"负责。

### 课程地图 — 六个部分,24 个模块

> 一张地铁线路图:先一眼看到所有线路,再一站一站地坐过去。

课程从思维走到实践。先讲架构师怎么思考(**mindset**)。再讲系统内部怎么搭(**structure**),以及系统分散到网络上之后会变成什么样(**distribution**)。接着是质量属性(**quality attributes**)、和人打交道(**people**),最后是这个职业的走向(**future**)。

_Diagram: 24 个模块分六个部分:mindset → structure → distribution → quality → people → future。_

怎么读:每个概念都有它的**英文术语**(查文档、面试时听到的就是它)、一个日常画面、一张**图**,以及一张带"是什么 / 例子 / 代价 / 关联"几栏的**卡片**。课程结尾有一份速查表,把所有概念放在一页里。

### Long-term thinking — 架构师以年为单位思考

> 工程师造一座明天就有车开过去的桥。架构师则问:等卡车开始上桥会怎样、等隔壁起了一个新城区会怎样、等维修费比桥本身还贵会怎样。

工程师通常解决**被提出来的那个问题**。架构师则先检查这个问题有没有被提对。这不是清高,也不是想"聊点高深的"。只不过一个架构决策,通常比一行代码更难改、改起来更贵。

脑子里最关键的那个转变:你不再只问"我该怎么做 X",而开始问**"我们为什么需要 X、有哪些备选、会失去什么、三年后它会是什么样"**。

_Diagram: 工程师盯着被提出来的任务。架构师把同一个问题拉长到几年。_

#### Long-term Thinking (以年为单位思考)

- **是什么:** 评判一个决策时,不只看"明天能不能用",还看它在一到三年里的表现。
- **怎么做:** 先问"为什么要 X"、有哪些备选、会失去什么,再问"怎么造 X"。
- **例子:** 那座桥:能不能扛住卡车、隔壁的新城区、比桥本身还贵的维修?
- **注意:** 这不是"为了增长而提前建设"的许可证——只是一个必须问的问题。
- **关联:** **Reversible decisions**、**trade-offs**、**technical debt**。

### Reversible vs irreversible decisions — 为系统的形态负责

> 木匠把搁板装歪了,你能挪。设计师把承重墙放错了位置,房子要带着这个错误活上几十年。

架构师没有义务亲自做每一个技术决策。但他有义务为那些"定下系统形态"的决策负责:service boundary、数据模型、集成协议、scaling 策略、安全、observability、迁移路径。

这里有用的区分是 **reversible decisions(可逆决策)** 和 **irreversible decisions(不可逆决策)**。可逆决策一天或一周就能改:一个日志库、一个按钮颜色、一个小模块的内部结构。不可逆或难以撤销的决策,会改变系统的轨迹:数据库、tenancy 模型、一个 API contract、event 的存储方式、service boundary。

_Diagram: 可逆的决策要快做。不可逆的决策决定系统的形态——由架构师负责。_

#### Reversible vs Irreversible (两类决策)

- **是什么:** 按撤销的代价给决策分类:几天或几周,还是几个月或几年。
- **怎么做:** 可逆的:快速决定,别争太久。不可逆的:慢慢来,摆出备选方案,并留下书面记录。
- **例子:** 日志库是可逆的。tenancy 模型或 API contract 几乎不可逆。
- **注意:** 在一个可逆的细节上争论一周,和一小时就定下一个不可逆的决策,一样糟。
- **关联:** **Data model**、**API contract**、**service boundaries**。

> **核心问题:** 如果这个决策一年后被证明是错的,有多少人、多少系统要为它付账?

#### 架构师的提问

- 我们真正在解决的是什么问题?
- 如果负载涨 10 倍,什么会变?
- 这里哪些决策可逆,哪些几乎不可逆?
- 一年后谁来维护这套东西?

> 架构师不造"最正确的系统"——他造一个能在现实里存活下来的系统。

## 2. Trade-offs 与决策

架构里没有免费的决策。每个选择都用一个属性的代价,买来另一个属性。

### Trade-off — 脱离 context 的 best practice 是坏建议

> 泥泞路上 SUV 赢轿车。城里轿车赢 SUV。要是只走三个街区又没地方停车,自行车赢过两者。

你没法脱离 context 去评判一个架构决策。REST、GraphQL、Kafka、PostgreSQL、Kubernetes、microservices、monolith——全都是带价格的工具。一个 **trade-off** 是一次有意识的交换:latency vs throughput、consistency vs availability、time-to-market vs 可维护性、简单 vs 灵活。

架构师不说"Kafka 比 RabbitMQ 好"。他说:"我们需要按 key 的 event ordering、高吞吐、replay,以及一个持久化的 log;作为交换,我们付出 Kafka 的运维复杂度。"专业的语言,听起来就是这样。

_Diagram: 每个决策同时落在好几条轴上的某一点。往一端靠,另一端就得让出点什么。_

#### Trade-offs (一次有意识的交换)

- **是什么:** 用一个属性的代价买来另一个属性的选择——而且要说出口。
- **怎么做:** 说清楚得到什么、失去什么,以及为什么现在这个价格可以接受。
- **例子:** "按 key 的 ordering、吞吐、replay、持久化 log——代价是 Kafka 的运维复杂度。"
- **注意:** 不带 context 的"X 比 Y 好"不是论据,只是口味。
- **关联:** **CAP / PACELC**、**over-engineering**、**sync vs async**。

### Over-engineering vs under-engineering — 复杂度是一种税

> 每多一个抽象,就像房子里多一个房间。房间可能有用,但得打扫、供暖、维修,还得向客人解释。

当复杂度买来了重要的属性——故障隔离、独立发布、安全、扩展、可测试性——它就是有用的。但"为了未来"而加的复杂度,通常会变成一笔永久的税。

**over-engineering** 和 **under-engineering** 这两个词,只有放在一起才有用。Over-engineering:复杂度出现在真正的痛点之前。Under-engineering:系统已经撞上了上限,而架构没给增长留余地。架构师的工作不是选一个极端,而是守住中间。

_Diagram: 两端各有一种错误,中间才是对的区域:只有当复杂度买来某个具体属性时,它才站得住。_

#### Over- / Under-engineering (过度与不足)

- **是什么:** Over:复杂度先于真正的痛点出现。Under:系统撞到上限,却没有增长的路。
- **怎么做:** 每加一层或一个服务,都要说出它买来的是哪个属性。
- **例子:** 上线就为 200 个用户上 microservices 和 Kafka——over。负载涨 10 倍时,一张表没有索引——under。
- **注意:** "为了未来"的复杂度不会消失——你每天都在为它付钱。
- **关联:** **Trade-off**、**technical debt**、**cognitive load**。

> **每个决策都该问:** 我们得到什么,失去什么,为什么现在这个价格可以接受?

#### 延伸阅读

- Architecture: The Hard Parts — Ford, Richards, Sadalage, Dehghani
- Software Architecture in Practice — Bass, Clements, Kazman
- Martin Fowler: Is Design Dead?

## 3. 技术债

技术债并不总是坏事。坏的是那种意外背上、没有记录、也没打算还的债。

### Technical debt — 是贷款,不是垃圾

> 为买设备贷款,能让生意提速。为办派对贷款,只会留下宿醉和利息。

**Technical debt(技术债)**是一个"现在让我们更快、却在将来产生修改成本"的决策。有时这是对的:要试水市场、要赶截止日期、要留住客户。问题出在团队把所有东西都贴上"债"的标签:烂代码、缺测试、混乱的依赖、过时的库。

一个有用的模型是 **Martin Fowler 的 Technical Debt Quadrant**:deliberate / inadvertent(有意 / 无意),prudent / reckless(审慎 / 鲁莽)。有意且审慎的债:"我们暂时不做 multi-region,因为只有 200 个用户、一个市场。"无意且鲁莽的债:"我们不知道自己需要 transaction。"

_Diagram: Fowler 的象限:债可以有意且审慎地背(右上)。左边的格子才是危险的。_

#### Technical Debt (拿未来的修改成本去借)

- **是什么:** 一个现在让我们更快、却在将来产生修改成本的决策。
- **怎么做:** 有意地背,记下是什么、为什么,并设一个回头复查的触发条件。
- **例子:** "暂时不做 multi-region:200 个用户,一个市场"——有意且审慎。
- **注意:** 如果什么都叫债——烂代码、没测试、旧的库——这个词就什么也不表示了。
- **关联:** **Debt interest**、**ADR**(记录决策)、**trade-off**。

### Debt interest — 债是要付利息的

> 路坏了,每趟出行都多花五分钟。一个月后它就不再是个坑,而是对整座城市征收的一笔税。

技术债的利息,就是之后每一次改动都变慢。团队越常碰某个区域,这笔债就越贵。所以热点 domain 里的债,比一个很少用的后台管理页面里的债更危险。

架构师要谈出一套还债策略:到底什么算债、它带来什么风险、什么时候回头复查决策、什么信号说明"该还了"。没有这些,"技术债"就成了一种情绪化的争论,而不是一件被管理的工具。

_Diagram: 同一笔债:在热点 domain 里,每次改动都在收利息;在后台管理页面里,几乎不收。_

#### Debt Interest (债的持续成本)

- **是什么:** 带着债的代码里,之后每一次改动都变慢。
- **怎么做:** 利息 ≈ 变慢的程度 × 团队碰这块区域的频率。
- **例子:** 每周都在改的支付里的债,比一个每季度才打开一次的后台管理页面里的债更贵。
- **注意:** 没有还债策略,"技术债"就成了一种情绪化的争论。
- **关联:** **Technical debt**、**cognitive load**。

> **关键问题:** 这笔债是在给我们买时间,还只是在掩盖一个糟糕的决策?

#### 债务复盘的问题

- 我们为什么做了这个妥协,记在哪里了?
- 每个 sprint 我们在付多少利息?
- 要发生什么,这笔债才会变成阻塞?
- 有还债计划吗?至少有回头复查的触发条件吗?

## 4. 认知负荷

一个没人能装进脑子里的系统,即使有很强的工程师也会出问题。

### Cognitive load — 架构必须装得进一个人的脑子

> 地铁线路图有用,不是因为它画出了隧道里的每颗螺丝。它画的是线路、换乘和行进方向。这就够做决定了。

**Cognitive load(认知负荷)**是系统要求工程师付出的脑力。有 **intrinsic load**:domain 本身的复杂度。也有 **extraneous load**:由糟糕的架构、不明显的联系、偶然的例外和"魔法"带来的额外复杂度。架构师消除不了业务本身的复杂度,但有义务降低偶然的复杂度。

好的模块可以单独理解。好的 bounded context 有自己的语言和自己的 invariant。好的团队拥有一块能装进自己脑子里的领域。

_Diagram: Domain 的复杂度(intrinsic)永远在。架构决定往上堆多少额外的复杂度(extraneous)——以及这一切装不装得进一个脑袋。_

#### Cognitive Load (脑力负担)

- **是什么:** 工程师要安全地改动某处,得在脑子里装下多少 context。
- **怎么做:** Intrinsic 是 domain 自带的复杂度——它留着。Extraneous 是偶然的——砍掉它。
- **例子:** 一个能单独理解的模块,对比一个离开"魔法"和各种例外就什么都讲不通的模块。
- **注意:** 再强的工程师,也救不了一个装不进脑子的系统。
- **关联:** **Bounded context**、**Conway's Law**、**decomposition**。

### Conway's Law — 团队结构会变成系统结构

> 如果三个部门互不沟通,它们的产品几乎总是看起来像三个产品被硬塞进了同一个菜单。

**Conway's Law** 说:一个组织设计出来的系统,会复刻该组织内部的沟通结构。如果架构的边界和团队的边界对不上,团队就会不断闯进别人的地盘。

**Team Topologies** 给出了几个有用的标签:stream-aligned team、platform team、enabling team、complicated-subsystem team。这不是 HR 的时髦词——它是一件架构工具。有时,正确拆分系统要从正确拆分职责开始。

_Diagram: Conway's Law:系统映射出团队的沟通方式。Team Topologies 给出四种团队类型,让你有意识地去切分。_

#### Conway's Law (加上 Team Topologies)

- **是什么:** 系统会映射出构建它的组织的沟通结构。
- **怎么做:** 让团队的边界和系统的边界对齐:每个团队拥有自己的领域。
- **例子:** 三个互不沟通的部门,交付的是"同一个菜单里的三个产品"。
- **注意:** 边界对不上,团队就会不断踩到彼此的地盘。
- **关联:** **Team Topologies**(stream-aligned、platform、enabling、complicated-subsystem)、**bounded context**。

> **关键问题:** 一个工程师要安全地改动这个组件,需要往脑子里装多少 context?

## 5. 系统拆分

只有当你能沿着正确的边界切开,一个大系统才变得可控。

### Cohesion & coupling — 按层切、按 domain 切,还是按数据流切

> 屠夫不会随便乱剁一头牲畜——他沿着关节下刀。好的拆分也在找自然的断点。

拆分不是"分到不同文件夹"。它是选择责任在哪里结束。**High cohesion(高内聚)**意味着组件里的一切都围绕同一件事。**Low coupling(低耦合)**意味着组件对其他组件知道得很少。

横向切得到 **layers(层)**:UI、application、domain、infrastructure。纵向切得到 feature 或 **bounded context**:billing、identity、catalog、comments。真实系统两种都需要,但把一种当成另一种,很危险。

_Diagram: 横向切得到层,纵向切得到 feature 和 context。真实的系统是两者交织的网格。_

#### Cohesion & Coupling (内紧外松)

- **是什么:** 组件内部的一切都围绕同一件事(cohesion);它对其他组件知道得很少(coupling)。
- **怎么做:** 横向(layers)和纵向(feature / context)都要切——但别把两者混淆。
- **例子:** 一个 `billing` 模块,自带 UI → application → domain → infra 几层。
- **注意:** "分到不同文件夹"还算不上拆分。
- **关联:** **Bounded context**、**layers**、**modular monolith**。

### Bounded context — 糟糕的边界会在改动中暴露自己

> 如果换厨房水龙头得把卧室拆开,问题就不在水龙头。

拆分糟糕的一个迹象:一个小改动波及五个模块。另一个迹象:一个模块因为不同的原因而改——今天因为产品,明天因为数据库,后天因为某个集成。

DDD 里的 **bounded context** 正是用在这里。它是一条边界,边界内每个词只有一种含义。billing 里的"Customer"和 support 里的"Customer"可以是不同的模型。硬把它们并成一个,往往会造出一个脆弱的"万能"模型。

_Diagram: 一个词,两个模型:billing 里的 Customer 和 support 里的 Customer 含义不同——这没问题。_

#### Bounded Context (DDD 的语义边界)

- **问题:** 一个小改动波及五个模块;一个模块因为不同的原因而改。
- **思路:** 一条边界,边界内每个词只有一种含义、一个模型。
- **例子:** billing 里的"Customer"和 support 里的"Customer"是不同的模型。
- **代价:** 要在边界处做模型映射;context 之间要做转换。
- **何时使用:** 当同一个概念,对业务的不同部分意味着不同的东西。
- **关联:** **Cohesion / coupling**、**Conway's Law**、**microservices**。

> **拆分测试:** 我能不能在不告诉你半个系统的情况下,解释清楚这个组件?

## 6. 分层与依赖的方向

业务逻辑应该依赖业务规则,而不是框架、数据库或 HTTP。

### Dependency Rule 与 DIP — 依赖朝中心流动

> 地基不该依赖窗帘的颜色。要是依赖了,这房子就是反着设计的。

**Clean Architecture**、**Hexagonal Architecture** 和 **Onion Architecture** 说的是同一件事:重要的逻辑放在中心,细节放在外围。HTTP、SQLAlchemy、Next.js、Kafka、Redis、Stripe——这些都是细节。它们重要,但不该决定业务规则。

**Dependency Inversion Principle** 说:高层逻辑依赖抽象,具体实现从外部接入。落到代码上就是 port 和 adapter:service 接收一个 `PaymentGatewayProtocol`,而不是在内部自己构造一个 `StripeClient`。

_Diagram: 中心的 domain 只依赖一个 port。StripeClient、HTTP 和数据库是外部的细节;所有箭头都指向内部。_

#### Dependency Inversion (DIP,依赖规则)

- **问题:** 业务规则已经长进了框架和数据库——没法测试,也没法替换。
- **思路:** 高层逻辑依赖抽象;具体实现从外部接入。
- **例子:** service 接收一个 `PaymentGatewayProtocol`,而不是自己构造 `StripeClient`。
- **代价:** 多出更多接口和文件。
- **何时使用:** 有真正的业务规则,需要比技术更替活得更久。
- **关联:** **Clean / Hexagonal / Onion Architecture**、**ports and adapters**。

### Abstraction — 为吸收变化而存在,不是为了好看

> adapter 该放在插头真正不一样的地方。如果你只因为喜欢 adapter,就在每个插座上都装一个,房子反而更不安全。

不是每个类都需要接口。但凡是碰到外部世界、时间、随机性、网络、文件系统或数据库的东西,在测试里都应该能被替换。否则你测的就不是业务逻辑——而是在单元测试里启动了一个小型的生产系统。

一层的代价是更多文件、更多字。回报是可测试性、可替换的基础设施、清晰的边界。如果没有回报,这一层就是累赘。

_Diagram: 把外部世界(时间、随机性、网络、文件、数据库)藏在 port 后面,测试里换成 fake。纯逻辑不需要接口。_

#### Abstraction (接口放在变化推动的地方)

- **是什么:** 接口放在实现真的会变、或会妨碍测试的地方。
- **怎么做:** 时间、随机性、网络、文件、数据库 → 放在 port 后面;测试里用 fake,生产里用真实的 adapter。
- **例子:** 一条业务规则不需要数据库、HTTP 或真实时间就能测——只要几毫秒。
- **注意:** 没有回报的一层,只是多余的文件和字,不是架构。
- **关联:** **DIP**、**testability**、**over-engineering**。

> **关键问题:** 我能不能在没有数据库、HTTP 和真实时间的情况下测试这条业务规则?

## 7. API 与 contract

API 是一份承诺。毁约很容易;重建客户端的信任很难。

### API contract — contract 比传输方式更重要

> 护照检查不在乎门是什么牌子。它在乎的是接受哪些证件、适用哪些规则、出问题时会发生什么。

**REST**、**GraphQL**、**gRPC** 和 **WebSocket** 是不同的沟通方式。REST 适合面向资源的 API 和简单的缓存。GraphQL 在不同客户端需要不同形状的读取时有帮助。gRPC 适合有类型的服务间通信。WebSocket 用于实时的双向事件。

选哪种传输方式是次要的。重要的是 **API contract**:schema、错误、idempotency、versioning、**backward compatibility**、**deprecation policy**。

_Diagram: 左边的传输方式可以换。右边的 contract 是对客户端的承诺——而且毁约代价很高。_

#### API Contract (对客户端的承诺)

- **是什么:** 一份承诺:schema、错误、idempotency、版本、兼容性,以及淘汰旧东西的规则。
- **怎么做:** 先谈妥 contract,再为这个任务挑传输方式。
- **例子:** REST 用于资源和缓存;GraphQL 用于多变的读取;gRPC 用于有类型的服务间通信;WebSocket 用于实时。
- **注意:** 毁约很容易;重建客户端的信任很难。
- **关联:** **Idempotency**、**versioning**、**reversible vs irreversible**。

### Idempotency — 它让你在真实世界里不翻车

> 你按十次电梯按钮,电梯不该来了又走十次。它应该明白意图只有一个。

**Idempotency(幂等)**是指重复一次操作,产生相同的最终效果。在联网的系统里,重试不可避免:超时、retry、重复的 webhook、worker 重启。如果创建支付、订单或消息不是幂等的,系统迟早会多做点什么。

架构师在 contract 层面设计幂等:**idempotency key**、unique constraint、安全的重试、清晰的状态。

_Diagram: 客户端用同一个 key 发了三次请求——服务端认出了这个 key,只创建了一笔支付。_

#### Idempotency (重复也安全的操作)

- **是什么:** 重复一次操作,得到与第一次调用相同的最终效果。
- **怎么做:** 请求里带 idempotency key + 数据库里的 unique constraint + 清晰的状态。
- **例子:** 支付超时 → 客户端用同一个 key 重发 `POST /payments` → 只扣一次款。
- **注意:** 没有它,重复(retry、重复的 webhook、worker 重启)迟早会多做点什么。
- **关联:** **At-least-once delivery**、**API contract**、**retries**。

> **关键问题:** 如果客户端把这个请求重复三次,会发生什么?

## 8. 数据模型

代码会被重写。数据要迁移好几年。所以数据模型是最贵的架构决策之一。

### Data model — 数据比应用活得更久

> 外墙一周就能重刷。地基浇错了,会在整栋楼的寿命里一直提醒你。

语言、框架、UI 可能换好几轮。但表、event、key、关系和历史记录会留下来。数据模型里的一个错误,往往会变成产品每个未来版本里的错误。

**Normalization(规范化)**减少重复和数据不一致的风险。**Denormalization(反规范化)**加快读取,并简化 read model。**OLTP** 系统为事务优化,**OLAP** 为分析优化。你没法设计出一个模型,让它完美地同时服务这两类工作。

_Diagram: 应用重写了三次;数据留了下来。下方:两种不同的工作——事务(OLTP)和分析(OLAP)。_

#### Data Model (加上 OLTP / OLAP)

- **是什么:** 表、event、key、关系和历史——代码每次重写之后依然留下的东西。
- **怎么做:** 规范化意味着更少重复、更少不一致;反规范化意味着更快的读取。
- **例子:** 订单用一个 OLTP 数据库,报表用另一个独立的 OLAP 存储。
- **注意:** 数据模型里的一个错误,会变成产品每个未来版本里的错误。
- **关联:** **Irreversible decisions**、**event sourcing**、**CQRS**。

### Event sourcing 与 CQRS — 一件强力工具,不是一种信仰

> 银行对账单不只存当前余额——它存的是操作的历史。正是这个,让你能解释余额是怎么来的。

**Event sourcing** 把变更存成一串 event,再从中重建当前状态。当历史、审计、状态恢复和 replay 很重要时,它很有用。代价很高:event schema 的演进、查询的复杂度、eventual consistency、要求很高的工具链。

**CQRS** 把写模型和读模型分开。它能大幅简化复杂的读取系统,但会带来同步和延迟。仅仅因为"听起来很有架构感"就引入 CQRS,是把一个问题变成两个问题的昂贵做法。

_Diagram: 命令把 event 写进 log;projection 据此构建 read model,查询读的是它。_

#### Event Sourcing (把状态存成 event 的 log)

- **问题:** 当前状态无法说明我们是怎么走到这一步的。
- **思路:** 把变更存成 event log;状态 = 把它们重放一遍。
- **例子:** 银行对账单:余额是各笔操作之和。
- **代价:** event schema 的演进、更难的查询、eventual consistency、要求很高的工具链。
- **何时使用:** 历史、审计、状态恢复和 replay 很重要的时候。
- **关联:** 常与 **CQRS** 搭配。

#### CQRS (Command Query Responsibility Segregation)

- **问题:** 一个模型既服务不好写入,也服务不好复杂的读取。
- **思路:** 把写模型和读模型分开。
- **例子:** 订单写进规范化的模型;报表页面读一份现成的视图。
- **代价:** 两个模型之间的同步和延迟。
- **何时使用:** 当读取确实复杂的时候——而不是因为"听起来很有架构感"。
- **关联:** **Event sourcing**、**denormalization**、**eventual consistency**。

> **关键问题:** 这个系统里,哪些事实必须撑过应用的三次重写?

## 9. 状态与真相的来源

状态是住在某个地方的复杂度。真相的副本越多,出错的方式就越多。

### Single source of truth — 不等于只有一个数据库

> 一家公司可以有很多报表,但必须只有一份官方的工资名册。否则发薪日就成了一场闹剧。

**Single source of truth** 的意思是:对每一个重要的事实,你都知道权威版本住在哪里。缓存、搜索索引、read replica、materialized view——这些都是派生出来的副本。它们可以很有用,但不该假装自己是原始的真相。

**Stateful(有状态)**组件持有状态,需要小心对待:事务、锁、恢复、备份。**Stateless(无状态)**组件更容易扩展和替换,但它们依然依赖住在别处的状态。

_Diagram: 左边是 stateless 的应用,中间是一个权威数据库,右边是派生的副本——它们不是真相。_

#### Single Source of Truth (每个事实只有一个所有者)

- **是什么:** 对每一个重要的事实,都清楚它的权威版本住在哪里。
- **怎么做:** 一份主记录;缓存、索引、replica 和 view 都是派生的副本。
- **例子:** 报表有很多份,但官方的工资名册只有一份。
- **注意:** 它不是"所有东西用一个数据库"——而是"每个事实只有一个所有者"。
- **关联:** **Stateful / stateless**、**cache**、**CQRS**。

### Cache 与 locking — 偶尔会撒谎的第二份真相

> 冰箱上贴的日程很方便,直到有人在手机日历里把会议改了时间。

**Cache(缓存)**用新鲜度换速度。缓存最难的问题不是"怎么放进去",而是"什么时候失效"。**TTL**、**explicit invalidation**、**write-through**、**write-behind**、**cache-aside**——这些是在复杂度、新鲜度和负载之间的不同折中。

_Diagram: Cache-aside:先读缓存,未命中就去数据库,再把结果放进缓存。右侧:失效策略。_

当两个操作争抢同一份状态时,就会出现 **race condition**。**Optimistic locking** 说:"冲突很少,我们在写入时检查版本。"**Pessimistic locking** 说:"冲突很危险,我们事先就加锁。"两者在不同条件下都是对的。

_Diagram: 左边,optimistic locking 在写入时靠版本号发现冲突。右边,pessimistic locking 让第二个写入者等待。_

#### Cache Invalidation (缓存,以及让它保持新鲜)

- **是什么:** 把数据的一份副本放到离读取者更近的地方:用新鲜度换速度。
- **怎么做:** TTL、explicit invalidation、write-through、write-behind、cache-aside——不同的折中。
- **例子:** 冰箱上的日程 vs 手机上的日历。
- **注意:** 主要的问题不是"怎么放进去",而是"什么时候扔掉"。
- **关联:** **Single source of truth**、**eventual consistency**。

#### Optimistic vs Pessimistic Locking (处理竞争的两种方式)

- **是什么:** 防止两个操作搞坏共享状态(race condition)的两种方式。
- **怎么做:** Optimistic:写入时检查版本。Pessimistic:事先加锁。
- **例子:** 两个经理编辑同一条记录:第二个会收到冲突,并重新读取数据。
- **注意:** Optimistic 在冲突频繁时吃力;pessimistic 在锁竞争严重时吃力。
- **关联:** **Transactions**、**stateful components**。

> **关键问题:** 每个事实的真相住在哪里,如果副本出现分歧会发生什么?

## 10. 分布式系统

分布式解决规模的问题,却带来了时间、顺序和网络信任的问题。

### CAP 与 PACELC — 网络是不确定性的来源

> monolith 是一间屋子里的对话。分布式系统则是身处不同时区的人之间的通信——信有时会丢,会到两次,或者迟到。

在分布式系统里,你不能只是"调用一个函数"。服务之间隔着网络、timeout、retry、**partial failure**、不同的时钟、不同版本的代码。在 monolith 里原本是本地事务的东西,变成了一套协调协议。

**CAP theorem** 常被糟糕地概括。实践中有用的版本是:在 **network partition** 期间,你要在 consistency 和 availability 之间二选一。**PACELC** 补充说:即使没有 partition,你依然要在 latency 和 consistency 之间选。

_Diagram: 节点之间的链路断了——选 consistency 还是 availability。没断——你依然要选:latency 还是 consistency。_

#### CAP / PACELC (分布式系统的取舍)

- **是什么:** 出现 partition 时:C 或 A(CAP)。没有时:L 或 C(PACELC)。
- **怎么做:** 每个存储、每个场景,都明确说出自己放弃什么。
- **例子:** 一个与对端断开的节点:拒绝响应(C),或者响应但可能是旧数据(A)。
- **注意:** 网络上不存在"我们选了 CA":partition 一定会发生;唯一的问题是那时你怎么办。
- **关联:** **Consistency models**、**trade-off**、**partial failure**。

### Consistency models 与 Saga — consistency 有不同的口味

> 两个人看同一份 Google Doc,几乎瞬间就看到相同的内容。两个人看一笔银行转账,可不能满足于"几乎"。

**Strong consistency** 给你新鲜的真相,但要付出 latency 和 availability 的代价。**Eventual consistency** 容忍暂时的不一致,但往往扩展得更好。**Causal consistency** 保留因果关系:如果 event B 依赖 A,B 就不该先于 A 变得可见。

_Diagram: consistency 的刻度:从 eventual(能扩展,容忍不一致)到 strong(新鲜,但更贵)。_

**Saga pattern** 把一个长的业务事务拆成若干步骤,并配上 **compensating actions(补偿动作)**。**Two-phase commit** 能提供跨资源的原子性,但在真实的分布式系统里既昂贵又脆弱。架构师挑的不是"最严格"的模型,而是足以应对风险的那个。

_Diagram: Saga:第 3 步失败了——补偿动作按相反的顺序撤销已完成的第 2 步和第 1 步。_

#### Consistency Models (strong · causal · eventual)

- **是什么:** 所有人以多快的速度、按什么顺序看到同一份真相。
- **怎么做:** Strong:永远新鲜。Causal:保持因果顺序。Eventual:稍后追平。
- **例子:** 共享文档:"几乎瞬间"就行。银行转账:不行。
- **注意:** Strong 付出的是 latency 和 availability;eventual 付出的是暂时的不一致。
- **关联:** **CAP / PACELC**、**cache**、**CQRS**。

#### Saga (带补偿的长事务)

- **问题:** 一个业务操作横跨多个服务;没有共享的事务。
- **思路:** 一串步骤;如果某一步失败,补偿动作撤销已经做过的部分。
- **例子:** 预留库存 → 扣款 → 发货失败 → 退款 → 释放预留。
- **代价:** 每一步都需要一个补偿;中间状态是可见的。
- **何时使用:** 用来替代 **two-phase commit**——后者跨网络既昂贵又脆弱。
- **关联:** **Eventual consistency**、**idempotency**、**microservices**。

> **关键问题:** 到底什么必须立刻保持一致,什么可以允许稍后再追平?

## 11. 单体与微服务

微服务不会修好糟糕的设计。它们只是把本地的问题变成分布式的问题。

### Modular monolith — 常被低估的起点

> 一座组织得好的仓库,比十座小仓库更快——如果反正是同一个快递员在它们之间来回跑。

monolith 不是一句骂人话。糟糕的 monolith 是一团乱糟糟的依赖。好的 **modular monolith** 有严格的内部边界、分开的 domain、明确的接口,以及单一的 **deployment unit**。对小团队来说,这往往是最好的起点。

**Microservices** 在你需要独立发布、自治的团队、不同的扩展特征、故障隔离,或不同技术栈时有帮助。代价是:网络调用、observability、分布式事务、DevOps 开销、versioning、服务之间的安全。

_Diagram: 左边,一个 deploy unit,内部有坚固的墙。右边,独立的服务,各有自己的数据库,还有一张网络的账单。_

#### Modular Monolith (一次部署,坚固的墙)

- **问题:** monolith 变成了一团乱糟糟的依赖。
- **思路:** 严格的内部边界和明确的接口——但仍是一个 deployment unit。
- **例子:** billing、identity、catalog 模块在同一个应用里,通过模块 API 互相通信。
- **代价:** 需要对边界保持纪律;作为一个整体扩展和发布。
- **何时使用:** 小团队、早期产品。**Microservices**——当你需要独立发布、自治的团队、不同的负载特征、故障隔离时。
- **关联:** **Bounded context**、**microservices**、**Strangler Fig**。

### Distributed monolith 与 Strangler Fig — 两头最糟的结合,以及出路

> 就像住在各自独立的公寓里,却每拿一把勺子都要跑去隔壁。

**Distributed monolith** 看起来像 microservices,但服务并不自治:共享的数据库 schema、同步调用链、只能一起发布的版本、牵一发动全身的改动。团队付出了分布式的成本,却没得到独立性。

_Diagram: Distributed monolith:服务被同步调用链和共享的 schema 捆在一起——没有独立性。_

**Strangler Fig pattern** 让你能小心地迁移:把旧系统包起来,逐步抽出能力,沿着真实的业务边界切,而不是沿着表去切。

_Diagram: Strangler Fig:旧系统前面的 facade 把能力逐个迁到新服务里,直到旧系统消失。_

#### Distributed Monolith (只在名字上是微服务)

- **迹象:** 共享的数据库 schema、同步调用链、只能一起发布、每个改动都牵连所有人。
- **为什么有害:** 你为分布式付了钱,却没得到独立性。
- **例子:** 各自独立的公寓,每拿一把勺子都要去隔壁。
- **出路:** 每个服务拥有自己的数据、异步连接、按业务含义划分边界——或者退回到 modular monolith。
- **关联:** **Modular monolith**、**bounded context**、**sync vs async**。

#### Strangler Fig (逐步替换)

- **问题:** 旧系统没法一次性重写。
- **思路:** 用 facade 把它包起来,逐步把能力迁到新服务里。
- **例子:** 先抽出 billing,再抽 catalog——由 facade 来路由请求。
- **代价:** 两套系统会并存很长一段时间。
- **何时使用:** 迁移一个在线的系统,又不想"推倒重来"。
- **关联:** 沿着 **bounded contexts** 切,而不是沿着表切。

> **关键问题:** 我们用 microservices 在治什么病:负载、团队、发布、容错——还是只是无聊?

## 12. 同步与异步

Sync 在时间上把系统耦合在一起。Async 打破这种耦合,但带来延迟、重复和顺序的复杂性。

### Queue 与 pub/sub — queue 不只是"稍后"

> 打电话要求双方此刻都有空。信可以稍后再读——但它可能丢、可能迟到,或者要求回复。

**同步(sync)**调用容易理解:发出去,等回答。但如果接收方慢或者不可用,受苦的是发送方。通过 **queue** 或 **pub/sub** 做 **async**,能让你扛过故障和负载尖峰,但它要求另一套思维模型。

queue 通常把工作分给多个 consumer。pub/sub 则把一个 event 广播给许多订阅者。Kafka、RabbitMQ、SQS、Redis Streams——不同的工具,有不同的顺序、持久性、replay 和运维模型。

_Diagram: Sync:发送方等待。Queue:每条消息交给一个 worker。Pub/sub:每个 event 送达所有订阅者。_

#### Queue vs Pub/Sub (分发工作 vs 广播)

- **问题:** 同步调用在时间上把系统耦合起来:接收方挂了,发送方受苦。
- **思路:** queue 把工作分给多个 consumer;pub/sub 把 event 发给每个订阅者。
- **例子:** Kafka、RabbitMQ、SQS、Redis Streams——顺序、持久性、replay 各不相同。
- **代价:** 延迟、重复、顺序的复杂性。
- **何时使用:** 发送方不需要立刻拿到结果;你必须扛过故障和尖峰。
- **关联:** **At-least-once delivery**、**backpressure**、**idempotency**。

### At-least-once delivery — exactly-once 几乎总是营销话术

> 如果一封信寄了两次,正经的会计不该把工资发两遍。

实践中,你按 **at-least-once delivery** 加上 **idempotent consumer** 来设计。一条消息可能到两次;handler 必须扛得住。**Dead letter queue** 用来存放在合理次数的尝试之后仍无法处理的消息。

**Backpressure** 是"接收方跟不上了"的信号。没有它,系统会优雅地接收一切,然后在内部死掉。

_Diagram: 消息可能到两次——consumer 按 id 丢弃重复项。失败 N 次后进入 DLQ。Backpressure 请求发送方放慢速度。_

#### At-least-once Delivery (加上 DLQ 和 backpressure)

- **是什么:** 保证消息一定会到,但可能不止一次。
- **怎么做:** idempotent consumer 丢弃重复项;dead letter queue 在 N 次失败后收集消息;backpressure 说"我跟不上了"。
- **例子:** 一条重复的"发工资"消息,不会把工资发两次。
- **注意:** "Exactly-once"几乎总是营销话术;没有 backpressure,系统会接收一切,然后在内部死掉。
- **关联:** **Idempotency**、**queue / pub/sub**、**reliability**。

> **关键问题:** 发送方是否需要立刻知道结果,还是只要把工作可靠地放进管道就够了?

## 13. 性能与度量

"让它快一点"不是需求。需求听起来像 latency、throughput、percentile 和负载特征。

### Tail Latency — 平均响应时间几乎总是在撒谎

> 如果九个人一分钟内拿到了餐,第十个人等了一个小时,平均值看起来还能忍。第十个人不这么认为。

这就是 **tail latency** 的概念。先说三个词。**Latency** 是单次操作的延迟。**Throughput** 是系统在单位时间内处理多少次操作。**Percentile** 展示分布:**p50** 是中位数,**p95** 表示 95% 的请求比这个值快,**p99** 是长尾。

用户感受不到平均值——他们感受到的是长尾。当一个页面依赖好几个后端调用时,tail latency 尤其重要:慢响应的概率会叠加起来。

_Diagram: 大多数请求很快,但尾部(p95、p99)很长。平均值把它藏起来了;用户却感受得到。_

#### Tail Latency (p95 / p99)

- **问题:** 平均响应时间看起来没问题,而有些用户等了非常久。
- **思路:** 看 percentile:p50 是中位数,p95 和 p99 是长尾。
- **例子:** 一个页面由五个后端调用拼成:只要其中任何一个落进长尾,整个页面就慢。
- **代价:** 你收集的是一个分布,而不是一个数字;指标里需要 histogram。
- **何时使用:** 每当你把"快"变成需求,或要建一个 SLO 的时候。
- **关联:** 是 **SLO** 的基础;在 **retry storm** 期间会变大;通过 **observability** 能看到。

### Profiling — 先做 profile,再优化

> 不做 profile 就去修性能,就像只看一张鞋子的照片就给病人看病。

这就是 **profiling** 的实践:先度量,再优化。N+1 查询、多余的 round trip、糟糕的索引、序列化、锁竞争、GC 停顿、冷缓存——它们看起来都是"慢",但治法各不相同。

**Amdahl's Law** 提醒你:加速比受限于你真正优化的那部分系统。**Little's Law** 帮你思考队列:在吞吐不变的情况下,latency 越大,系统内部积压的在途工作就越多。

_Diagram: 循环:度量 → profile → 找到热点 → 只改一处 → 再度量。下方是两条界定你预期的定律。_

#### Profiling (先度量,再优化)

- **问题:** 团队"加速"了错的地方:花了一周,系统并没有更快。
- **思路:** profile 显示时间真正花在哪里。只修热点。
- **例子:** "慢页面"原来是一个 N+1 查询,而不是语言慢——用一条查询加一个 join 就解决了。
- **代价:** 度量和工具要花时间;你需要一个可复现的负载特征。
- **何时使用:** 任何优化之前。没有 profile,就不优化。
- **关联:** **Amdahl's Law** 是加速比的天花板;**Little's Law** 把 latency、throughput 和队列联系起来。

> **关键问题:** 在真实负载下,哪个指标会最先变红?

## 14. 无需重写的可扩展性

可扩展性,是在负载和功能上增长,而不必从头重建系统的能力。

### Vertical vs Horizontal Scaling — 它们买到的东西不同

> 你可以买一辆大卡车。也可以买十辆小的。前者更简单,后者更灵活——但需要一个调度员。

**Vertical scaling**(scale up)是把一台机器变强。在还有余量时很简单。**Horizontal scaling**(scale out)是增加更多实例。更灵活,但需要 stateless 的应用层、load balancing、一套共享状态的策略和 observability。

数据库比应用更难扩展。**Read replica** 帮的是读,不是写。**Sharding** 能拆分数据,但迫使你选一个 shard key。糟糕的 shard key 会把扩展变成负载失衡。

_Diagram: 左:一台机器不断变强,直到撞上硬件的天花板。右:一个 load balancer 和许多完全相同的 stateless 副本。下方:扩展数据库的两种方式。_

#### Vertical vs Horizontal Scaling (scale up / scale out)

- **问题:** 负载在增长,一台机器很快就不够用了。
- **思路:** Vertical:让机器更强。Horizontal:在 load balancer 后面放更多机器。
- **例子:** stateless 的应用很容易复制;数据库更难:读用 read replica,写按 shard key 做 sharding。
- **代价:** Vertical 会撞上天花板。Horizontal 需要 stateless 层、load balancing、共享状态策略和 observability。
- **何时使用:** 有余量时用 vertical。需要越过它继续增长时用 horizontal。
- **关联:** **Scale Cube** 的 X 轴;状态模块里的 **stateless** 组件;**Single Source of Truth**。

### Scale Cube — 扩展功能和扩展负载同样重要

> 一座城市不只靠人口增长。它靠学校、医院、道路和规则增长。

系统要吸收的不只是更多流量,还有更多的产品面。如果每个新功能都要重写一个共享的 God-service,这个系统在组织层面就无法扩展。

这里有用的模型是 **scaling cube**:**X 轴**是克隆,**Y 轴**是按功能拆分,**Z 轴**是按数据或 tenant 分区。

_Diagram: 三条增长的轴:X——同一个应用的多份副本,Y——不同功能放在不同部分,Z——数据或客户被切成分区。_

#### Scale Cube (X / Y / Z 三条轴)

- **问题:** 大家只想着流量,但系统其实死于功能增长:所有东西都住在一个 God-service 里。
- **思路:** 沿三条轴增长:X——克隆,Y——按功能拆分,Z——按数据或 tenant 拆分。
- **例子:** X:应用的三份副本。Y:billing、catalog、identity 各自分开。Z:tenant A–H 在一个 shard,I–P 在另一个。
- **代价:** 每多一条轴,就多出一批你必须守住并观测的边界。
- **何时使用:** 当你问"什么会最先坏"——而答案不只是 CPU 的时候。
- **关联:** Y 轴就是 **Bounded Context** 和 microservices;Z 轴是 sharding;留意 **Conway's Law**。

> **关键问题:** 什么会最先坏:CPU、数据库、团队、发布,还是数据模型?

## 15. 故障下的可靠性

可靠的系统不是什么都不会坏的系统。可靠的系统知道坏了该怎么办。

### Retry、Circuit Breaker、Bulkhead — 没有策略的 retry 是对自己的攻击

> 门打不开,敲一次是合理的。每秒敲一千次,就成了门和邻居的麻烦。

**Retry** 用于瞬时的故障。但 retry 需要上限、timeout、**exponential backoff** 和 **jitter**。否则,局部故障就会变成 **retry storm**。

**Circuit breaker** 会暂时停止对一个卡住的依赖发起调用。**Bulkhead pattern** 隔离资源:如果某个 downstream 挂了,它不会把你所有的 worker 线程和连接池一起拖下水。

_Diagram: 上:停顿逐渐变长、再加随机偏移的 retry。下:独立的池(bulkhead)和一个 circuit breaker——卡住的 Payments API 不会把搜索一起拖垮。_

#### Retry + Circuit Breaker + Bulkhead (对失败依赖的韧性)

- **问题:** 一个依赖变慢或挂了;盲目的 retry 把它彻底压垮,还吃光服务的所有资源。
- **思路:** 有上限、timeout、backoff 和 jitter 的 retry。Circuit breaker:不再调用已死的依赖。Bulkhead:把池分开,让故障不扩散。
- **例子:** 支付 API 挂住了:breaker 断开并快速失败,而搜索的池照常工作。
- **代价:** 更多的配置和状态;你得决定依赖不可用时系统如何降级。
- **何时使用:** 任何对可能挂住的东西发起的网络调用。
- **关联:** retry 只有配合 **Idempotency** 才安全;与 **at-least-once** 和 **backpressure** 相关。

### SLI / SLO / SLA — SLO 把可靠性变成一份合约

> "我们会尽力"不是列车时刻表。时刻表会说明什么算正常,什么算故障。

**SLI** 是你度量的东西:成功率、latency、新鲜度。**SLO** 是目标:30 天内 99.9% 的请求成功。**SLA** 是对客户的承诺,并附带后果。

**Error budget** 告诉你,你被允许花掉多少不可靠性。如果预算烧光了,团队就停止追功能,转而修可靠性。这是用业务语言讲出来的工程质量。

_Diagram: SLI 是度量,SLO 是目标,SLA 是对客户的承诺。SLO 为 99.9% 时,error budget 是 0.1%——每 30 天约 43 分钟。_

#### SLI / SLO / SLA (error budget)

- **问题:** 没人度量过"可靠",所以功能 vs 质量的争论永远没完。
- **思路:** 度量(SLI),约定目标(SLO),把对客户的承诺单独放着(SLA)。离 100% 的差距就是 error budget。
- **例子:** SLO:30 天内 99.9% 的请求成功。预算烧光——功能暂停。
- **代价:** 你得诚实地选择度量什么,并在预算用完时守住这条规则。
- **何时使用:** 当需要用同一种语言和业务讨论可靠性的时候。
- **关联:** SLI 常建立在 **Tail Latency** 之上;告警针对**症状**触发;它是用数字表达的 **NFR**。

> **关键问题:** 当某个依赖不可用时,系统如何降级?

## 16. 安全是一种生活方式

你不能在发布前夕才加上安全。它必须内建在访问模型、数据、基础设施和流程里。

### Authentication vs Authorization — 是两回事

> 护照证明你是谁。车票证明你可以去哪里。一个不能代替另一个。

**Authentication**(AuthN)回答"这是谁?"。**Authorization**(AuthZ)回答"他被允许做什么?"。把两者搞混很危险:一个用户可能正确地登录了,却依然无权查看别人的发票。

**RBAC** 通过角色授予权限。**ABAC** 考虑属性:资源所有者、tenant、地区、对象状态。基于 session 的 auth 更容易撤销;**JWT** 无需数据库就更容易验证,但更难撤销。选哪个取决于威胁和生命周期。

_Diagram: 一个请求要经过两个不同的步骤:authentication(“你是谁?”——护照)和 authorization(“你能做什么?”——车票)。已登录不等于被允许。_

#### Authentication vs Authorization (AuthN / AuthZ)

- **问题:** "用户已登录"被当成"用户被允许"——结果他们读到了别人的数据。
- **思路:** 两个独立的步骤:AuthN 确认身份,AuthZ 检查对某个具体资源的权利。
- **例子:** 一个已登录的客户请求另一个客户的 42 号发票——AuthZ 必须返回 403。
- **代价:** 选择模型:RBAC 更简单,ABAC 更精确;session 容易撤销,JWT 容易验证但难撤销。
- **何时使用:** 任何数据有所有者的系统。
- **关联:** **defense in depth** 的一层;**STRIDE** 里的 spoofing 和 elevation-of-privilege 威胁。

### Threat Modeling — 职业性的偏执

> 好的架构师像攻击者一样思考,不是因为不信任人,而是因为信任现实。

**Threat modeling** 是在攻击发生之前,系统地寻找威胁。**STRIDE** 帮你去找:spoofing、tampering、repudiation、information disclosure、denial of service、elevation of privilege。**Defense in depth** 意味着多层保护:输入校验、authz、加密、audit log、rate limiting、least privilege。

**Secrets management** 不是"把密钥放进 env 就不管了"。你需要理解轮换、访问、审计、blast radius,以及泄漏时会发生什么。

_Diagram: 左:STRIDE 的六类威胁,每个组件都要过一遍。右:分层的保护——一层被攻破,其他层依然在。_

#### Threat Modeling (STRIDE, defense in depth)

- **问题:** 安全是在发布前夕才想起来的,而那时访问模型已经来不及改了。
- **思路:** 让组件过一遍 STRIDE,并设置多层保护,而不是一把"主"锁。
- **例子:** 泄漏的一把密钥:它能打开什么(blast radius)、你多快能轮换它、它在审计里出现在哪。
- **代价:** review 要花时间,secrets management 要有纪律。
- **何时使用:** 在设计每个新组件和集成的时候。
- **关联:** **AuthN vs AuthZ** 是一层保护;**observability** 和 audit log 让你能发现攻击。

> **关键问题:** 如果攻击者拿到了这个组件的访问权限,他能做什么?

## 17. Observability 与运维

你修不了你看不见的东西。系统必须能解释自己的状态,而不需要在代码里考古。

### Observability — monitoring 告诉你哪里不对,observability 帮你理解为什么

> "check engine"灯很有用,但修理工仍然需要仪表、历史记录和诊断工具。

**Monitoring** 回答"有地方不对了"。**Observability** 回答"为什么"。observability 的三大支柱:**logs**、**metrics**、**traces**。Structured logging 让日志可以被查询。Metrics 展示聚合数据。**Distributed tracing** 跟踪一个请求穿过各个服务的路径。

**Correlation ID** 是个能救命的小东西:同一个请求 ID 贯穿 gateway、服务、queue、worker 和 downstream。

_Diagram: 三大支柱——logs、metrics、traces。同一个 correlation ID 穿过每一跳,所以整条请求路径都能被还原。_

#### Observability (logs · metrics · traces, correlation ID)

- **问题:** 夜里出了故障,却要靠在代码里考古来找原因。
- **思路:** 系统自己解释自己的状态:结构化的日志、聚合的指标、请求路径的 trace。
- **例子:** 有了 request id `req-7f3a`,你能在 gateway、queue 和 worker 里找到这个请求——并看到它卡在哪儿。
- **代价:** 工具、数据存储,以及把 ID 传到每个角落的纪律。
- **何时使用:** 从第一天起,尤其是带 queue 的分布式系统。
- **关联:** **SLI** 和 **Symptom-based Alerting** 的数据来源;没有它,**Profiling** 就是盲人摸象。

### Symptom-based Alerting — 按症状告警

> 用户不在乎 CPU 是 92%。他们在乎的是结账用不了。

好的告警说的是用户的痛点——这就是 **symptom-based alerting**:错误率、latency、支付失败、数据过期。坏的告警说的是一个完全可能是正常的原因。如果告警嘈杂,团队就会不再信任它们。

**Runbook** 回答"凌晨 3 点该做什么"。**Post-mortem** 回答"我们怎么确保这不再发生"。**Blameless**:目标是改进系统,而不是找出罪人。

_Diagram: 像 CPU 这样的原因往往是正常的,还会制造噪音。告警要建立在用户痛点的症状之上,后面跟着 runbook 和 blameless post-mortem。_

#### Symptom-based Alerting (runbook, blameless post-mortem)

- **问题:** 告警按原因触发,团队习惯了噪音,结果漏掉了真正的故障。
- **思路:** 只有用户受苦时才叫醒人;每条告警都配一份 runbook。
- **例子:** 不是"CPU 92%",而是"支付失败数上升"——再配一份 runbook:先查什么。
- **代价:** 你得知道自己的症状有哪些,并让 runbook 保持最新。
- **何时使用:** 用于所有值班工程师要在夜里处理的事。
- **关联:** 症状就是 **SLI**;post-mortem 和 **ADR**、**RFC** 属于同一种书面文化。

> **关键问题:** 如果这个东西夜里坏了,有哪些数据能帮我们在十分钟内找到原因?

## 18. 活着的文档

一个没有记录下来的架构决策,会随着它作者的 context 一起消失。

### ADR — Architecture Decision Record 胜过记忆

> 口头约定,在第一次休假、第一次离职或半年后的第一次争论之前都管用。

**Architecture Decision Record** 记录 context、决策、备选方案和后果。ADR 不必是一部小说。好的 ADR 回答:我们在解决什么问题、选了什么、为什么没选其他备选、什么时候回头复查。

文档不是为了审计——而是为了下一次改动。新来的工程师需要理解的不只是"怎么做",还有"为什么"。

_Diagram: 一份 ADR,一个决策:context、选择、备选方案、后果,以及何时复查。半年后,新来的人能了解“为什么”。_

#### ADR (Architecture Decision Record)

- **问题:** 决策只存在于作者的脑子里;作者走了,没人知道当初为什么这么做。
- **思路:** 每个决策一份简短的记录:context、决策、备选方案、后果。
- **例子:** "我们选 PostgreSQL,不选 MongoDB:我们需要事务;如果出现文档型的负载就复查。"
- **代价:** 半小时的写作。比半年后的那场争论便宜。
- **何时使用:** 用于撤销代价很高的决策。
- **关联:** 记录 **Trade-offs** 和技术债;与 **RFC** 并列;是 **Learning in Public** 的一部分。

### C4 Model — 图是工作工具,不是装饰

> 地铁线路图不画电线和螺栓。它画的是线路和换乘。这就足以不迷路。

**C4 model** 给你四个层级:**Context**、**Container**、**Component**、**Code**。通常前三个就够了。当交互的顺序很重要时,**sequence diagram** 很有用。很少需要完整的 UML;零散的几张图则是需要的。

**Living documentation(活文档)**和代码放在一起,在 PR 里更新,并被 review。被遗忘的 wiki 里的文档,很快就会变成历史小说。

_Diagram: C4 的四个缩放层级——通常三个就够。活文档和代码放在一起;被遗忘的 wiki 会变成小说。_

#### C4 Model (活文档)

- **问题:** 图是为了好看而画的:所有东西挤在一张图里,一个月后它就在撒谎。
- **思路:** 按缩放层级来画:Context → Container → Component(→ Code,很少用)。
- **例子:** Context:系统、用户、外部服务。Container:web、API、数据库、queue。
- **代价:** 图要和代码在同一个 PR 里更新。
- **何时使用:** 当需要向新来的人或另一个团队解释这个系统时。
- **关联:** 展示 **Bounded Contexts** 和边界;sequence diagram 负责调用的顺序。

> **关键问题:** 半年后,新来的人能理解我们为什么这么做吗?

## 19. 沟通与说服

架构师要和工程师、产品、业务、安全和法务打交道。每个听众听的是不同的语言。

### Risk Translation — 把风险翻译成听众的语言

> 医生对病人不会只说拉丁文的名字。他会解释哪里会疼、疗程多长、要避免什么。

这就是 **risk translation** 的实践。你可以对工程师说:"支付 callback 上没有 idempotency。"业务需要听到的是:"如果 webhook 重试,我们可能重复扣款,或者把商品发两次;这是财务和声誉上的风险。"

**Stakeholder mapping** 帮你看清谁在乎什么:产品要 time-to-market,安全要降低风险,财务要可预测的成本,客服要可理解的行为,工程要可维护性。

_Diagram: 同一个风险,对工程师和对业务听起来不一样。stakeholder 地图展示了每一方在乎什么。_

#### Risk Translation (stakeholder mapping)

- **问题:** 技术风险听起来像行话,业务根本听不进去。
- **思路:** 把风险翻译成对这个具体听众有意义的后果。
- **例子:** "callback 上没有 idempotency" → "我们可能重复扣款——一个财务和声誉上的风险。"
- **代价:** 你得知道每个 stakeholder 在乎什么,并说他们的语言。
- **何时使用:** 每当一个决策取决于工程之外的人。
- **关联:** 例子是 **Idempotency**;业务那一侧是 **NFR** 和 **cost of delay**。

### RFC — 书面提案能给争论降温

> 白板前的争论,往往是嗓门最大的人赢。书面提案则迫使论点脱离嗓门,独立站得住。

**RFC**(Request for Comments)描述问题、约束、各种选项、选定的方案、风险和 rollout 计划。好的 RFC 会在决策变成规定之前,邀请大家来批评。

**Disagree and commit** 只有在一场诚实的讨论之后才成立。如果团队没被听见,那不是 commit——而是压制。

_Diagram: RFC 的各个部分和决策的路径:草稿 → 带批评的 review → 决定 → disagree and commit,但前提是每个人都被听见了。_

#### RFC (Request for Comments, disagree and commit)

- **问题:** 白板前的争论,赢的是嗓门最大的,而不是最有道理的。
- **思路:** 以书面形式:问题、约束、选项、决策、风险、rollout。批评发生在决策之前。
- **例子:** 换用新的 queue 之前:一份 RFC、一周的评论,然后是决策和一份 ADR。
- **代价:** 比"开个会就定了"更慢;需要一个负责人把它推到决策。
- **何时使用:** 用于涉及多个团队、或撤销代价很高的决策。
- **关联:** 结果记入 **ADR**;是 **Influence without Authority** 的一件工具。

> **关键问题:** 我该怎么向一个不认同我技术优先级的人解释这个决策?

## 20. 无职权的领导力

架构师往往仍是个人贡献者。他的权威来自清晰的思考、信任,以及让团队变得更强的能力。

### Influence without Authority

> 指挥不会演奏每一件乐器,但没有指挥,乐团很快就会变成一群各弹各曲的才子。

这就是 **influence without authority** 的概念。架构师不该成为每个决策的瓶颈。他的工作是定原则、讲清边界、培养人,并在犯错代价很高的地方出手。

架构师的 **mentoring** 不是"照我做的做"。而是教会提问:你见过哪些备选方案、放弃了什么、打算怎么验证风险、责任到哪里为止。

_Diagram: 左:架构师是瓶颈——每个决策都在排队。右:放大器——原则、边界和问题,由各团队来做决定。_

#### Influence without Authority (通过提问来 mentoring)

- **问题:** 架构师亲自拍板所有事,成了每个团队都要排的队。
- **思路:** 通过原则、边界和问题来施加影响,而不是下命令。
- **例子:** 不说"就这么做",而是问:"你看到了哪些备选方案,打算怎么验证风险?"
- **代价:** 当下更慢;需要花几年才能建立起来的信任。
- **何时使用:** 当你没有直属下属、但决策仍然要做的时候。
- **关联:** 通过 **RFC** 和 **ADR** 起作用;反面是 **Ivory Tower Architect**。

### Delegation with Guardrails — 委派不等于转移责任

> 船长可以把舵交出去,但不能说船的航向不再是他的事。

一部分架构决策应该由各团队来做。否则架构师就成了一条队。但必须有 **guardrails(护栏)**:原则、review、ADR、平台约束、quality gate。

当一个决策不可逆、破坏系统的 invariant、带来安全风险,或影响多个团队时,架构师出手。

_Diagram: 团队在护栏之内自己做决定。架构师只在右边这四个信号出现时出手。_

#### Delegation with Guardrails (责任仍在)

- **问题:** 要么所有事都经过架构师(排队),要么没人对航向负责。
- **思路:** 团队在护栏之内自己决定;架构师对航向负责,并在明确的信号出现时出手。
- **例子:** 团队自己挑日志库;改 tenancy 模型则只能和架构师一起做。
- **代价:** 护栏要被表述清楚、写下来,并尽可能自动检查。
- **何时使用:** 当有不止一个团队、却只有一个架构师时。
- **关联:** "不可逆"这个信号来自 **Reversible vs Irreversible**;护栏就是 **ADR** 和 quality gate。

> **关键问题:** 我是在放大团队做决策的能力,还是在替团队做决定?

## 21. 业务与产品

架构只有放在业务目标的 context 里才有意义。否则它就是昂贵的工程审美。

### Non-functional Requirements — 同样是业务需求

> 对一家商店来说,"黑色星期五结账能用"不是技术细节——而是钱。

**Non-functional requirements**(NFR)——性能、可用性、安全、合规、可运维性——不是"技术上的想要"。它们是产品的属性。架构师把它们翻译成业务影响:损失的收入、监管风险、客服成本、上市的速度。

**Cost of delay** 帮你讨论的不只是做这件事的成本,还有等待的成本。有时需要一笔没有即时 ROI 的架构投入,因为没有它,接下来的功能会开始变得更贵。

_Diagram: 左:non-functional requirements。右:它们对业务意味着什么。架构师是中间的翻译。_

#### Non-functional Requirements (NFR, cost of delay)

- **问题:** 系统质量被当成"技术上的想要",一再被推后。
- **思路:** NFR 是产品属性;用收入、风险和成本来讨论它们。
- **例子:** "结账能撑过黑色星期五"是钱,不是细节。
- **代价:** 你得用数字估算影响,哪怕只是粗略的。
- **何时使用:** 当架构工作和功能争抢时间的时候。
- **关联:** 用数字表达:**SLO**;被推后的工作:**Technical Debt**;沟通的语言:**Risk Translation**。

### Build vs Buy — 不是面子问题

> 不是每家公司都该为了开灯,自己造一座发电站。

**自建(Build)**给你控制权和差异化。**购买(Buy)**给你速度,并降低运维负担。**Vendor lock-in** 是真实的风险,但它本身并不构成反对购买的论据。问题是:如果供应商改价格、改 API 或降质量,退出要花多少?

架构师帮助产品看清 **capability map**:哪些能力是业务的核心,哪些更适合买。

_Diagram: 自建客户为之付钱的核心;购买通用的部分。对所有买来的东西,关键问题是退出成本。_

#### Build vs Buy (capability map, vendor lock-in)

- **问题:** 有人出于面子自己造,有人什么都买、却没有退出计划。
- **思路:** 自建能让业务差异化的部分;购买通用的部分。对所有买来的东西,估算退出成本。
- **例子:** 你自己的 domain 引擎——自建;邮件发送或支付——通常购买。
- **代价:** 自建:时间,以及永久的维护。购买:依赖供应商的价格、API 和质量。
- **何时使用:** 每次说"我们自己写一个"之前。
- **关联:** 陷阱是 **NIH 综合症**;**Dependency Inversion** 有助于隔离供应商。

> **关键问题:** 这个架构决策如何改变业务的速度、风险或成本?

## 22. AI-native 架构师

当代码由 agent 来写,架构师的价值转移到规格说明、架构思考和质量把控上。

### Specification-driven Development — spec 的质量成了瓶颈

> 如果施工队快了十倍,起约束作用的就不再是砌砖的速度。而是图纸的质量。

AI agent 加快了实现,但并没有消除架构。一个提得糟糕的任务,只是更快地变成糟糕的代码。**Specification-driven development** 的意思是:首要的产物是对行为、边界、invariant、测试和验收标准的规格说明。

在 AI-native 的流程里,架构师写更少的底层代码,写更多精确的文字。他审的是 diff、测试、架构边界,以及与 spec 的一致性。

_Diagram: spec 是主要的产物。agent 很快把它变成代码;架构师拿代码对照 spec 检查,不符时去修 spec。_

#### Specification-driven Development (把 spec 当作主要产物)

- **问题:** agent 写代码快十倍——造错东西也一样快。
- **思路:** 主要产物是 spec:行为、边界、invariant、测试、验收标准。
- **例子:** 不是"做支付",而是:场景、错误、"每个订单只有一笔支付"这条 invariant,以及针对它的测试。
- **代价:** 架构师写更精确的文字,更少的代码。
- **何时使用:** 只要由 AI agent 来做实现。
- **关联:** 由 **Agent Output Review** 来检查;invariant 来自 **API Contract** 和 **Bounded Context**。

### Agent Output Review — agent 的代码值得和人写的 PR 同样的纪律

> 一个每小时写一千行的快手 junior,依然需要 review。速度不会让代码变对。

你不能因为 agent 的代码能编译就接受它。你需要测试、eval、import 边界、类型检查、安全 review。**Scenario evaluations** 尤其重要:系统必须通过真实的用例,而不只是 happy path。

AI-native 的组织,赢的不是"人人都在 vibe-coding"的地方,而是有强规格说明、小步迭代、自动化检查和架构负责人的地方。

_Diagram: agent 的 PR 要过和人一样的 quality gate:测试、scenario eval、import 边界、类型、安全 review。_

#### Agent Output Review (给 AI 代码的 quality gate)

- **问题:** agent 的代码因为能编译、看起来很自信,就被接受了。
- **思路:** 和对人一样的自动化 quality gate,一样的 review。
- **例子:** Scenario eval 能发现 agent 只处理了 happy path,忘了重复支付的情形。
- **代价:** 自动化检查和 import 边界要提前搭好。
- **何时使用:** agent 的每一个 PR,没有例外。
- **关联:** 检查与 **Specification-driven Development** 的一致性;边界来自 **Dependency Inversion**。

> **关键问题:** spec 是否精确到,让一个快手执行者不会造错东西?

## 23. 架构师的反模式

架构师恰恰因为听起来有说服力,才可能很危险。了解这些陷阱能省下好几年。

### Ivory Tower Architect

> 有人在山顶上画了一座完美的城市,却从不走下山去看看人们实际往哪儿走。

这就是 **ivory tower architect(象牙塔架构师)**反模式:决策是在远离代码、运维和团队的地方做出的。图很漂亮,实现却遭殃。解药很简单:读 PR、看 incident、和工程师聊天、亲自走一遍改动的路径。

_Diagram: 塔离地面很远:离代码、incident 和工程师都很远。解药是走下来——读 PR、看 incident、聊天、走一遍改动的路径。_

#### Ivory Tower Architect (站在山上做架构)

- **症状:** 团队实现不了的漂亮图纸。
- **为什么有害:** 决策无视真实的代码、运维和人。
- **解药:** 读 PR、看 incident、和工程师聊天、亲自走一遍改动的路径。
- **关联:** **Influence without Authority** 的反面;形式压过现实,也属于 **Cargo Cult**。

### Resume-driven Development 与 Cargo Cult

> 如果你既没有水也没有消防员,却因为能给邻居留下印象而买一辆消防车,那就怪了。

**Resume-driven development** 为了简历上多一行而选技术。**Cargo cult engineering** 照搬别人决策的形式,却没有它背后的理由。**NIH 综合症**(Not Invented Here)逼你在成熟的外部工具本可以做得更好的地方,自己再写一套。

另一个陷阱是 **technical perfectionism(技术完美主义)**。那个从未到达用户手里的完美架构,会输给一个足够好、能交付、还能演进的系统。

_Diagram: 四个把你从真实问题上拉开的陷阱:为简历选技术、照搬形式、“我们自己造”,以及无休止的打磨。_

#### Resume-driven Development (cargo cult, NIH, 完美主义)

- **症状:** 决策的理由是时髦、别人的例子或美感——而不是某个问题。
- **为什么有害:** 你付出了复杂技术的代价,却没得到它的好处,或者压根没能到达用户。
- **例子:** 三个人的团队上 Kubernetes 和 microservices,"因为 Netflix 就这么干"。
- **解药:** 从问题出发,诚实地说出每个选项的代价。
- **关联:** 靠 **Trade-offs** 和 **Build vs Buy** 来治;常常催生 **Distributed Monolith**。

> **关键问题:** 我选这个决策,是为了解决问题,还是为了那种架构之美的感觉?

## 24. 持续成长

架构是一场无限游戏。技术会变,但提出正确问题的能力只会越来越强。

### T-shaped / M-shaped Skills — 从系统中学习,而不是从 hype 里学

> 厨师不是靠看刀具广告成长的,而是靠理解食材、火候、时间和味道。

新技术值得用一连串问题去学:它解决什么问题、引入什么代价、已经在哪里失败过、有哪些备选方案、要满足什么条件它才会变得有用。

_Diagram: 对任何新技术的五个过滤问题。问完之后,你再决定:现在就用,还是再等等。_

**T-shaped skills** 让你在一个领域有深度,在相邻领域有广度。**M-shaped skills** 出现在成熟的架构师身上:几根深的支柱——数据、分布式、安全、产品、AI 工具。

_Diagram: T:一个深的领域加上广度。M:成熟架构师身上的几根深支柱。_

#### T-shaped / M-shaped Skills (从系统中学习,而非 hype)

- **问题:** 人们学遍每一项时髦的技术,却没获得深度和判断力。
- **思路:** 通过"问题与代价"的提问去学技术;培养几根深的支柱。
- **例子:** 一个新数据库:它解决什么问题、已经在哪里失败过、要满足什么条件它才对我们有帮助。
- **代价:** 比读发布公告慢;深度要花好几年。
- **何时使用:** 雷达上每出现一项新技术的时候。
- **关联:** 防范 **Resume-driven Development** 的手段;这些支柱就是本课程的各个主题。

### Learning in Public — 加速成熟

> 当你把一个决策写出来讲清楚时,你思维里的漏洞会在生产环境发现它们之前就暴露出来。

**Learning in public** 的意思是公开地、用文字来学习。写 ADR、RFC、工程笔记、post-mortem,以及对决策的简短复盘。向比你强的架构师学习——不只是他们选了什么,还有他们是怎么推理的。

人们会去模仿的那类架构师,通常不是懂最多术语的那个。而是在他身边,团队开始问出更好问题的那个人。

_Diagram: 循环:决定 → 写下来 → 获得反馈 → 问出更好的问题。你思维里的漏洞,会在生产环境之前暴露。_

#### Learning in Public (用文字学习)

- **问题:** 生产环境会比你的同事更早发现你推理里的缺陷。
- **思路:** 把决策和复盘写下来,分享出去,从反馈中学习。
- **例子:** ADR、RFC、工程笔记、post-mortem、对决策的简短复盘。
- **代价:** 写作的时间,以及展示未完成想法的勇气。
- **何时使用:** 一直——架构师就是这样成长的。
- **关联:** 用到 **ADR**、**RFC** 和 **post-mortem**;和 **Influence without Authority** 一样,让团队更强。

> **最后一个问题:** 在打开编辑器之前,我现在会先问自己哪些问题?

#### 最终自查

- 我能不带自我防御地说出一个决策的 trade-off。
- 我明白系统里真相的来源在哪里。
- 我分得清可逆决策和不可逆决策。
- 我能用不同的话,向工程、产品和业务解释这套架构。
- 我写决策的方式,让它一年后仍然能被看懂。
- 我不会把复杂度藏在"best practice"这个词后面。

### 课程地图:一切如何相连

> 旅程终点的一张地铁线路图:现在你看到了线路在哪里交汇。

思维塑造结构、数据和质量。结构被记录进文档,质量由安全和运维来验证,文档和风险说服业务。这一切合在一起,扩展成 AI-native 的工作方式和个人成长。

_Diagram: 课程的八组概念及其关联:从思维到结构、数据和质量,再到文档、业务、运维和成长。_

## 速查表:本课程的全部 52 个概念

### 架构师的思维(1–4)

- **Long-term Thinking** (以年为单位思考): 先检查问题有没有被提对,以及这个决策三年后会怎样。
- **Reversible vs Irreversible** (两类决策): 容易撤销的,快速决定;会改变系统轨迹的,慢慢决定。
- **Trade-offs** (一次有意识的交换): 每个选择都用一个属性的代价买来另一个属性。脱离 context 的 best practice 是坏建议。
- **Over- / Under-engineering** (复杂度是一种税): 只有当复杂度买来重要的属性时,它才站得住。守住中间。
- **Technical Debt** (是贷款,不是垃圾): 一个现在让你更快、以后花得更多的决策。
- **Debt Interest** (变慢): 之后每一次改动都变慢。热点区域里的债最贵。
- **Cognitive Load** (装得进一个脑袋): 要安全地改动一个组件,需要多少 context。去掉偶然的复杂度。
- **Conway's Law** (组织的形状 = 系统的形状): 系统会映射出组织的沟通结构。

### 系统结构(5–7、11)

- **Cohesion & Coupling** (内部只做一件事,对外知道得少): 组件内部——围绕同一件事;对外——尽量少了解别的组件。
- **Bounded Context** (DDD 的边界): 一条边界,边界内每个词只有一种含义。
- **Dependency Inversion** (依赖指向内部): 业务逻辑依赖抽象;细节从外部接入(ports and adapters)。
- **Abstraction** (为吸收变化而存在): 一层的价值在于吸收变化、让你无需外部世界就能测试,而不是看起来漂亮。
- **API Contract** (contract 高于传输方式): schema、错误、versioning 和兼容性,比选 REST、GraphQL 还是 gRPC 更重要。
- **Idempotency** (重复也安全): 重复一次操作,得到相同的最终效果。防范 retry 和重复。
- **Modular Monolith** (一次部署,坚固的墙): 一个有严格内部边界的应用。往往是最好的起点。
- **Distributed Monolith** (两头最糟的结合): 只能一起发布的服务。付出了分布式的成本,却没有独立性。
- **Strangler Fig** (一块一块迁移): 让新系统围绕旧系统生长,逐步把流量迁过去,而不是一次性推倒重写。

### 数据与分布式(8–10、12)

- **Data Model** (数据比代码活得久): 最贵的决策:规范化、OLTP vs OLAP。
- **Event Sourcing** (event 的 log 才是真相): 状态由 event 重建:完整的历史和审计,但运行和演进都很贵。
- **CQRS** (分开写模型和读模型): 命令和查询各有自己的模型;当读和写差别很大时有用。
- **Single Source of Truth** (一个权威版本): 对每个事实,你都知道主版本住在哪里;其余的都是派生的副本。
- **Cache Invalidation** (会撒谎的第二份真相): 用新鲜度换速度;难的问题是什么时候失效。
- **Optimistic vs Pessimistic Locking** (写时检查 vs 先加锁): 防止并发编辑互相覆盖的两种方式。
- **CAP / PACELC** (网络就是不确定性): 出现 partition 时:consistency 或 availability;否则:latency 或 consistency。
- **Consistency Models** (strong, eventual, causal): 不是最严格的模型,而是足以应对风险的那个。
- **Saga** (带补偿的一串步骤): 跨服务的长事务,每一步都有对应的撤销,而不是一把全局锁。
- **Queue vs Pub/Sub** (不只是"稍后"): Async 在时间上解耦,但带来延迟、重复和顺序问题。
- **At-least-once Delivery** (exactly-once 是营销话术): 一条消息可能到两次;consumer 必须是 idempotent 的。DLQ、backpressure。

### 系统质量(13–15)

- **Tail Latency** (p95 / p99): 用户感受到的是长尾,而不是平均值。
- **Profiling** (先度量): 只优化你找到的热点;Amdahl 和 Little 划定了界限。
- **Vertical vs Horizontal Scaling** (scale up / scale out): 一台大机器,或者 load balancer 后面许多完全相同的机器。
- **Scale Cube** (X / Y / Z): 克隆、按功能拆分、按数据拆分。
- **Retry + Circuit Breaker + Bulkhead** (韧性): 带 backoff 和 jitter 的 retry、对付已死依赖的 breaker、相互隔离的池。
- **SLI / SLO / SLA** (error budget): 度量、目标和承诺;error budget 决定是做功能还是保可靠性。

### 安全与运维(16–17)

- **Authentication vs Authorization** (AuthN / AuthZ): "你是谁?"和"你能做什么?"是两个不同的步骤。
- **Threat Modeling** (STRIDE, defense in depth): 在攻击之前找出威胁,并分层防御。
- **Observability** (logs · metrics · traces): 系统能解释"为什么";correlation ID 把整条路径串起来。
- **Symptom-based Alerting** (runbook, post-mortem): 按用户的痛点告警;runbook 说明该做什么;post-mortem 是 blameless 的。

### 文档与沟通(18–19)

- **ADR** (Architecture Decision Record): context、决策、备选方案、后果——每个决策一份。
- **C4 Model** (活文档): 按缩放层级画、和代码放在一起的图。
- **Risk Translation** (stakeholder mapping): 同一个风险,对工程师和对业务用不同的话说。
- **RFC** (disagree and commit): 一份书面提案,批评发生在决策之前。

### 领导力与业务(20–21)

- **Influence without Authority** (mentoring): 用原则、边界和问题,代替命令。
- **Delegation with Guardrails** (责任仍在): 团队做决定;遇到不可逆、invariant、安全和跨多团队影响时,架构师出手。
- **Non-functional Requirements** (NFR, cost of delay): 用收入、风险和成本来表达的产品属性。
- **Build vs Buy** (capability map): 自建核心,购买通用部分,清楚退出成本。

### AI-native、陷阱与成长(22–24)

- **Specification-driven Development** (把 spec 当作产物): 执行者越快,spec 的精确度就越重要。
- **Agent Output Review** (quality gate): agent 的代码要过和人写的 PR 同样的检查。
- **Ivory Tower Architect** (站在山上做架构): 在远离代码、运维和团队的地方做出的决策。
- **Resume-driven Development** (cargo cult, NIH, 完美主义): 为简历选技术、照搬形式、自己造本该买的东西、无休止的打磨。
- **T-shaped / M-shaped Skills** (从系统中学习): 深度加广度;成熟的架构师有几根深的支柱。
- **Learning in Public** (用文字学习): 把决策写下来,在生产环境之前看到你思维里的漏洞。

> 架构师,就是学会了在后果变成 incident 之前就看到它们的工程师。

课程结束 · 24 个模块 · 实践从这里开始。

请把这门课程不是当作参考手册,而是当作一组问题来重读。架构,始于你在一个决策面前停下来、诚实地说出每个选项的代价的那一刻。
