fedorthinks

完整课程 · No. 03

一门面向已经在写代码、但想学会做架构决策、向团队和业务解释这些决策、并看见系统全貌的工程师的课程。

24 个模块 · 52 个概念 · 每个都配一张图 · 结尾有速查表

详细模块
§ 01

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

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

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

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

Mindset · 1–401 Architect role02 Trade-offs03 Technical debt04 Cognitive loadStructure · 5–905 Decomposition06 Layers & DIP07 API contracts08 Data model09 State & truthDistribution · 10–1210 Distributed systems11 Monolith vs micro12 Sync vs asyncQuality · 13–1713 Performance14 Scalability15 Reliability16 Security17 ObservabilityPeople · 18–2118 Documentation19 Communication20 Leadership21 Business & productFuture · 22–2422 AI-native architect23 Anti-patterns24 Growth
24 个模块分六个部分:mindset → structure → distribution → quality → people → future。

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

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

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

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

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

Engineerhow to build X?solves the task as posedArchitectwhy X? · alternatives? · what we lose? · in 3 years?nowmonth1 year3 yearsan architectural decision costs more to change than a line of code
工程师盯着被提出来的任务。架构师把同一个问题拉长到几年。
ConceptLong-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。

Reversible · days or weeksLogging libraryButton colourSmall module internalsdecide fast · fix in a weekIrreversible · yearsDatabaseTenancy modelAPI contractHow events are storedService boundariesthe architect owns these
可逆的决策要快做。不可逆的决策决定系统的形态——由架构师负责。
ConceptReversible vs Irreversible· 两类决策
是什么
按撤销的代价给决策分类:几天或几周,还是几个月或几年。
怎么做

可逆的:快速决定,别争太久。不可逆的:慢慢来,摆出备选方案,并留下书面记录。

例子
日志库是可逆的。tenancy 模型或 API contract 几乎不可逆。
注意
在一个可逆的细节上争论一周,和一小时就定下一个不可逆的决策,一样糟。
关联
Data model、API contract、service boundaries。
架构师的提问
  • 我们真正在解决的是什么问题?
  • 如果负载涨 10 倍,什么会变?
  • 这里哪些决策可逆,哪些几乎不可逆?
  • 一年后谁来维护这套东西?

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

§ 02

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

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 的运维复杂度。"专业的语言,听起来就是这样。

every choice buys one property at the cost of anotherLatencyThroughputConsistencyAvailabilityTime-to-marketMaintainabilitySimplicityFlexibility
每个决策同时落在好几条轴上的某一点。往一端靠,另一端就得让出点什么。
ConceptTrade-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:系统已经撞上了上限,而架构没给增长留余地。架构师的工作不是选一个极端,而是守住中间。

Under-engineeringhit limits, no room to growRight-sizedcomplexity buys a propertyOver-engineeringcomplexity before real painlessmore complexitycomplexity pays off only when it buys a property:failure isolation · independent release · security · scaling · testability
两端各有一种错误,中间才是对的区域:只有当复杂度买来某个具体属性时,它才站得住。
ConceptOver- / 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?
§ 03

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

Technical debt — 是贷款,不是垃圾

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

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

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

RecklessPrudentDeliberateInadvertent“No time for design”corners cut on purpose“Skip multi-region”200 users, one market“What are transactions?”didn't know we needed them“Now we know better”learned while building
Fowler 的象限:债可以有意且审慎地背(右上)。左边的格子才是危险的。
ConceptTechnical Debt· 拿未来的修改成本去借
是什么
一个现在让我们更快、却在将来产生修改成本的决策。
怎么做
有意地背,记下是什么、为什么,并设一个回头复查的触发条件。
例子
"暂时不做 multi-region:200 个用户,一个市场"——有意且审慎。
注意
如果什么都叫债——烂代码、没测试、旧的库——这个词就什么也不表示了。
关联
Debt interest、ADR(记录决策)、trade-off。

Debt interest — 债是要付利息的

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

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

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

Hot domainAdmin panela changeinterest: the extra time each change costsinterest = slowdown × how often the team touches this code
同一笔债:在热点 domain 里,每次改动都在收利息;在后台管理页面里,几乎不收。
ConceptDebt Interest· 债的持续成本
是什么
带着债的代码里,之后每一次改动都变慢。
怎么做
利息 ≈ 变慢的程度 × 团队碰这块区域的频率。
例子
每周都在改的支付里的债,比一个每季度才打开一次的后台管理页面里的债更贵。
注意
没有还债策略,"技术债"就成了一种情绪化的争论。
关联
Technical debt、cognitive load。
债务复盘的问题
  • 我们为什么做了这个妥协,记在哪里了?
  • 每个 sprint 我们在付多少利息?
  • 要发生什么,这笔债才会变成阻塞?
  • 有还债计划吗?至少有回头复查的触发条件吗?
§ 04

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

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

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

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

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

what fits in a headMessy systemIntrinsicExtraneousClean systemIntrinsicExtraneousintrinsic = the domain itself (keep) · extraneous = accidental complexity (cut)
Domain 的复杂度(intrinsic)永远在。架构决定往上堆多少额外的复杂度(extraneous)——以及这一切装不装得进一个脑袋。
ConceptCognitive 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 的时髦词——它是一件架构工具。有时,正确拆分系统要从正确拆分职责开始。

OrganisationSystemTeam ATeam BTeam CPart APart BPart CmirrorsTeam Topologies — four team typesStream-alignedPlatformEnablingComplicated subsystem
Conway's Law:系统映射出团队的沟通方式。Team Topologies 给出四种团队类型,让你有意识地去切分。
ConceptConway's Law· 加上 Team Topologies
是什么
系统会映射出构建它的组织的沟通结构。
怎么做
让团队的边界和系统的边界对齐:每个团队拥有自己的领域。
例子
三个互不沟通的部门,交付的是"同一个菜单里的三个产品"。
注意
边界对不上,团队就会不断踩到彼此的地盘。
关联

Team Topologies(stream-aligned、platform、enabling、complicated-subsystem)、bounded context。

§ 05

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

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

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

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

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

BillingIdentityCatalogCommentsUIApplicationDomainInfrastructurehorizontal cut = layers · vertical cut = features / bounded contextshigh cohesion inside a part · low coupling between parts
横向切得到层,纵向切得到 feature 和 context。真实的系统是两者交织的网格。
ConceptCohesion & 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"可以是不同的模型。硬把它们并成一个,往往会造出一个脆弱的"万能"模型。

Billing contextSupport contextCustomerCustomerpayer of invoicespayment methodtax detailsperson with ticketscontact channelconversation history≠✗ one shared “universal” Customer → a brittle model
一个词,两个模型:billing 里的 Customer 和 support 里的 Customer 含义不同——这没问题。
PatternBounded Context· DDD 的语义边界
问题
一个小改动波及五个模块;一个模块因为不同的原因而改。
思路
一条边界,边界内每个词只有一种含义、一个模型。
例子
billing 里的"Customer"和 support 里的"Customer"是不同的模型。
代价
要在边界处做模型映射;context 之间要做转换。
何时使用
当同一个概念,对业务的不同部分意味着不同的东西。
关联
Cohesion / coupling、Conway's Law、microservices。
§ 06

业务逻辑应该依赖业务规则,而不是框架、数据库或 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。

Details (outside)Application (use cases)Domainbusiness rulesPaymentGatewayProtocolport · an abstractiondepends onHTTP · Next.jsSQLAlchemyKafka · RedisStripeClientimplements the portevery dependency arrow points inwarddetails plug infrom outside
中心的 domain 只依赖一个 port。StripeClient、HTTP 和数据库是外部的细节;所有箭头都指向内部。
PrincipleDependency Inversion· DIP,依赖规则
问题
业务规则已经长进了框架和数据库——没法测试,也没法替换。
思路
高层逻辑依赖抽象;具体实现从外部接入。
例子

service 接收一个 PaymentGatewayProtocol,而不是自己构造 StripeClient。

代价
多出更多接口和文件。
何时使用
有真正的业务规则,需要比技术更替活得更久。
关联
Clean / Hexagonal / Onion Architecture、ports and adapters。

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

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

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

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

Outside world → behind a portTimeRandomnessNetworkFile systemDatabasePortRealproductionFakeunit testsNo interface neededPure functionno outside worldInternal classone implementationno payoff →an extra layer is dead weight
把外部世界(时间、随机性、网络、文件、数据库)藏在 port 后面,测试里换成 fake。纯逻辑不需要接口。
PracticeAbstraction· 接口放在变化推动的地方
是什么
接口放在实现真的会变、或会妨碍测试的地方。
怎么做

时间、随机性、网络、文件、数据库 → 放在 port 后面;测试里用 fake,生产里用真实的 adapter。

例子
一条业务规则不需要数据库、HTTP 或真实时间就能测——只要几毫秒。
注意
没有回报的一层,只是多余的文件和字,不是架构。
关联
DIP、testability、over-engineering。
§ 07

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

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

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

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

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

RESTresources, cachingGraphQLflexible readsgRPCtyped service-to-serviceWebSocketreal-time, two-waytransportContract — what really mattersSchemaErrorsIdempotencyVersioningBackward compatibilityDeprecation policy
左边的传输方式可以换。右边的 contract 是对客户端的承诺——而且毁约代价很高。
ConceptAPI Contract· 对客户端的承诺
是什么

一份承诺:schema、错误、idempotency、版本、兼容性,以及淘汰旧东西的规则。

怎么做
先谈妥 contract,再为这个任务挑传输方式。
例子

REST 用于资源和缓存;GraphQL 用于多变的读取;gRPC 用于有类型的服务间通信;WebSocket 用于实时。

注意
毁约很容易;重建客户端的信任很难。
关联
Idempotency、versioning、reversible vs irreversible。

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

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

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

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

the same Idempotency-Key travels with every attemptClient1st trytimeout → retryretryAPIseen this key?1 paymentunique(idempotency_key)same key → same result · the card is charged once
客户端用同一个 key 发了三次请求——服务端认出了这个 key,只创建了一笔支付。
ConceptIdempotency· 重复也安全的操作
是什么
重复一次操作,得到与第一次调用相同的最终效果。
怎么做
请求里带 idempotency key + 数据库里的 unique constraint + 清晰的状态。
例子
支付超时 → 客户端用同一个 key 重发 POST /payments → 只扣一次款。
注意
没有它,重复(retry、重复的 webhook、worker 重启)迟早会多做点什么。
关联
At-least-once delivery、API contract、retries。
§ 08

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

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

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

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

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

App v1App v2 · rewriteApp v3 · rewriteData: tables · events · keys · historyoutlives every rewriteOLTPtransactions · normalizedOLAPanalytics · denormalized reads≠one model can't perfectly serve both kinds of work
应用重写了三次;数据留了下来。下方:两种不同的工作——事务(OLTP)和分析(OLAP)。
ConceptData 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,是把一个问题变成两个问题的昂贵做法。

CommandWrite modelOrderPlacedItemAddedOrderPaidevent log · append-onlyprojectionRead modelshaped for queriesQueryreadsEvent sourcing: state = replay of events · CQRS: separate write and read modelsprice: event schema evolution · lag · harder queries · tooling
命令把 event 写进 log;projection 据此构建 read model,查询读的是它。
PatternEvent Sourcing· 把状态存成 event 的 log
问题
当前状态无法说明我们是怎么走到这一步的。
思路
把变更存成 event log;状态 = 把它们重放一遍。
例子
银行对账单:余额是各笔操作之和。
代价

event schema 的演进、更难的查询、eventual consistency、要求很高的工具链。

何时使用
历史、审计、状态恢复和 replay 很重要的时候。
关联
常与 CQRS 搭配。
PatternCQRS· Command Query Responsibility Segregation
问题
一个模型既服务不好写入,也服务不好复杂的读取。
思路
把写模型和读模型分开。
例子
订单写进规范化的模型;报表页面读一份现成的视图。
代价
两个模型之间的同步和延迟。
何时使用
当读取确实复杂的时候——而不是因为"听起来很有架构感"。
关联
Event sourcing、denormalization、eventual consistency。
§ 09

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

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

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

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

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

App · statelessApp · statelessApp · statelesseasy to scale and replacePrimary DBauthoritative versionCacheSearch indexRead replicaMaterialized viewderived copiesstateful: transactions · locking · recovery · backup
左边是 stateless 的应用,中间是一个权威数据库,右边是派生的副本——它们不是真相。
ConceptSingle Source of Truth· 每个事实只有一个所有者
是什么
对每一个重要的事实,都清楚它的权威版本住在哪里。
怎么做
一份主记录;缓存、索引、replica 和 view 都是派生的副本。
例子
报表有很多份,但官方的工资名册只有一份。
注意
它不是"所有东西用一个数据库"——而是"每个事实只有一个所有者"。
关联
Stateful / stateless、cache、CQRS。

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

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

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

AppCachefast, maybe staleDatabasethe truth1 · read2 · on miss3 · fillTTLExplicit invalidationWrite-throughWrite-behindCache-asideinvalidation strategieshardest part: when to invalidate
Cache-aside:先读缓存,未命中就去数据库,再把结果放进缓存。右侧:失效策略。

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

Optimistic lockingA reads row (version 1)B reads row (version 1)A writes: v1 → v2 ✓B writes, expects v1 → conflictbet: conflicts are rarePessimistic lockingA takes the lockB waitsA writes, releases the lockB takes the lock, writesbet: conflicts are dangerous
左边,optimistic locking 在写入时靠版本号发现冲突。右边,pessimistic locking 让第二个写入者等待。
ConceptCache Invalidation· 缓存,以及让它保持新鲜
是什么
把数据的一份副本放到离读取者更近的地方:用新鲜度换速度。
怎么做

TTL、explicit invalidation、write-through、write-behind、cache-aside——不同的折中。

例子
冰箱上的日程 vs 手机上的日历。
注意
主要的问题不是"怎么放进去",而是"什么时候扔掉"。
关联
Single source of truth、eventual consistency。
ConceptOptimistic 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 之间选。

Node ANode B✕network partitionCAP: during a partition, pick oneConsistencyrefuse until in syncorAvailabilityanswer, maybe stalePACELC: else (no partition), still pick oneLatencyorConsistency
节点之间的链路断了——选 consistency 还是 availability。没断——你依然要选:latency 还是 consistency。
ConceptCAP / 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 变得可见。

Eventualtemporary drift is OKCausalB never shows before AStrongalways fresh truthscales better, tolerates driftfresher truth, costs latency + availabilityshared doc: “almost” is finebank transfer
consistency 的刻度:从 eventual(能扩展,容忍不一致)到 strong(新鲜,但更贵)。

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

Reserve stockstep 1Charge cardstep 2Ship orderstep 3 fails ✗Release stockundo 1Refund cardundo 2compensating actions undo the steps already done2PC instead: atomic across resources, but costly and fragile
Saga:第 3 步失败了——补偿动作按相反的顺序撤销已完成的第 2 步和第 1 步。
ConceptConsistency Models· strong · causal · eventual
是什么
所有人以多快的速度、按什么顺序看到同一份真相。
怎么做
Strong:永远新鲜。Causal:保持因果顺序。Eventual:稍后追平。
例子
共享文档:"几乎瞬间"就行。银行转账:不行。
注意
Strong 付出的是 latency 和 availability;eventual 付出的是暂时的不一致。
关联
CAP / PACELC、cache、CQRS。
PatternSaga· 带补偿的长事务
问题
一个业务操作横跨多个服务;没有共享的事务。
思路
一串步骤;如果某一步失败,补偿动作撤销已经做过的部分。
例子
预留库存 → 扣款 → 发货失败 → 退款 → 释放预留。
代价
每一步都需要一个补偿;中间状态是可见的。
何时使用
用来替代 two-phase commit——后者跨网络既昂贵又脆弱。
关联
Eventual consistency、idempotency、microservices。
§ 11

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

Modular monolith — 常被低估的起点

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

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

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

Modular monolithBillingmodule · explicit APIIdentitymodule · explicit APICatalogmodule · explicit APIone deploy unit · in-process callsMicroservicesBillingserviceown DBIdentityserviceown DBCatalogserviceown DBnetwork · versioning · ops overhead
左边,一个 deploy unit,内部有坚固的墙。右边,独立的服务,各有自己的数据库,还有一张网络的账单。
PatternModular Monolith· 一次部署,坚固的墙
问题
monolith 变成了一团乱糟糟的依赖。
思路
严格的内部边界和明确的接口——但仍是一个 deployment unit。
例子
billing、identity、catalog 模块在同一个应用里,通过模块 API 互相通信。
代价
需要对边界保持纪律;作为一个整体扩展和发布。
何时使用

小团队、早期产品。Microservices——当你需要独立发布、自治的团队、不同的负载特征、故障隔离时。

关联
Bounded context、microservices、Strangler Fig。

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

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

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

Service AService BService CsyncsyncShared schemaone databasereleases only together · pays for distribution, gets no independence
Distributed monolith:服务被同步调用链和共享的 schema 捆在一起——没有独立性。

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

ClientFacaderoutes requestsLegacy systemshrinks step by stepBillingCatalogAccountsnew services growcut by business capability, not by tables
Strangler Fig:旧系统前面的 facade 把能力逐个迁到新服务里,直到旧系统消失。
Anti-patternDistributed Monolith· 只在名字上是微服务
迹象
共享的数据库 schema、同步调用链、只能一起发布、每个改动都牵连所有人。
为什么有害
你为分布式付了钱,却没得到独立性。
例子
各自独立的公寓,每拿一把勺子都要去隔壁。
出路

每个服务拥有自己的数据、异步连接、按业务含义划分边界——或者退回到 modular monolith。

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

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

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

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

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

SyncSenderReceiversender waits; receiver down = sender suffersQueueProducerQueueone consumer eachWorker 1Worker 2Pub/subPublisherTopiccopy to everyoneSubscriber 1Subscriber 2Subscriber 3
Sync:发送方等待。Queue:每条消息交给一个 worker。Pub/sub:每个 event 送达所有订阅者。
PatternQueue 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 是"接收方跟不上了"的信号。没有它,系统会优雅地接收一切,然后在内部死掉。

ProducerQueueat-least-oncemay arrive twiceConsumerdedupe by msg idEffectexactly onceafter N retriesDead letter queuebackpressure: “slow down”“exactly-once delivery” is almost always marketing
消息可能到两次——consumer 按 id 丢弃重复项。失败 N 次后进入 DLQ。Backpressure 请求发送方放慢速度。
ConceptAt-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 尤其重要:慢响应的概率会叠加起来。

requestsp50 · medianp95p99tail latencylatency →one screen = many backend calls → the tail adds up
大多数请求很快,但尾部(p95、p99)很长。平均值把它藏起来了;用户却感受得到。
ConceptTail 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 越大,系统内部积压的在途工作就越多。

MeasureProfileFind hotspotN+1 · index · locksFix one thingre-measure · no profile, no optimisationAmdahl’s Lawoptimised partthe rest of the system · sets the limitLittle’s Law: work in progress = throughput × latency
循环:度量 → profile → 找到热点 → 只改一处 → 再度量。下方是两条界定你预期的定律。
PracticeProfiling· 先度量,再优化
问题
团队"加速"了错的地方:花了一周,系统并没有更快。
思路
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 会把扩展变成负载失衡。

Vertical · scale upceilingappbigger boxmore CPU / RAMHorizontal · scale outLoad balancerappstatelessappstatelessappstatelessRead replicashelp reads, not writesShardinga bad shard key = load skew
左:一台机器不断变强,直到撞上硬件的天花板。右:一个 load balancer 和许多完全相同的 stateless 副本。下方:扩展数据库的两种方式。
ConceptVertical 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 分区。

X-axiscloneappappappY-axissplit by functionbillingcatalogidentityZ-axispartition by datatenants A–Htenants I–Ptenants Q–Z
三条增长的轴:X——同一个应用的多份副本,Y——不同功能放在不同部分,Z——数据或客户被切成分区。
ConceptScale 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。

§ 15

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

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

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

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

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

retry = limit · timeout · exponential backoff · jittertry 1try 2try 3ok~1s~2s~4s + jitterno limit, no jitter → a retry stormService · bulkheadspool → paymentspool → searchCircuit breakeropen: fail fastPayments APIstuck / timing outSearch API
上:停顿逐渐变长、再加随机偏移的 retry。下:独立的池(bulkhead)和一个 circuit breaker——卡住的 Payments API 不会把搜索一起拖垮。
PatternRetry + 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 告诉你,你被允许花掉多少不可靠性。如果预算烧光了,团队就停止追功能,转而修可靠性。这是用业务语言讲出来的工程质量。

SLI · indicatore.g. success rateSLO · objective99.9% over 30 daysSLA · agreementpromise + penaltyError budget = 100% − SLO = 0.1% ≈ 43 min per 30 daysspent on incidentsleftBudget leftship featuresBudget burnedstop features, fix reliability
SLI 是度量,SLO 是目标,SLA 是对客户的承诺。SLO 为 99.9% 时,error budget 是 0.1%——每 30 天约 43 分钟。
ConceptSLI / 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 无需数据库就更容易验证,但更难撤销。选哪个取决于威胁和生命周期。

passportticketRequesttoken / sessionAuthenticationwho is this?Authorizationwhat may they do?Resourceinvoice #42Session vs JWTrevoke vs verifyRBAC / ABACroles / attributes403not your invoice
一个请求要经过两个不同的步骤:authentication(“你是谁?”——护照)和 authorization(“你能做什么?”——车票)。已登录不等于被允许。
ConceptAuthentication 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,以及泄漏时会发生什么。

STRIDE · find threatsSpoofingfake identityTamperingaltered dataRepudiation“it wasn’t me”Info disclosuredata leakDoSdenial of serviceElevationof privilegeDefense in depthrate limitinginput validationauthzleast privilegeencryptionaudit log
左:STRIDE 的六类威胁,每个组件都要过一遍。右:分层的保护——一层被攻破,其他层依然在。
PracticeThreat Modeling· STRIDE, defense in depth
问题
安全是在发布前夕才想起来的,而那时访问模型已经来不及改了。
思路
让组件过一遍 STRIDE,并设置多层保护,而不是一把"主"锁。
例子

泄漏的一把密钥:它能打开什么(blast radius)、你多快能轮换它、它在审计里出现在哪。

代价
review 要花时间,secrets management 要有纪律。
何时使用
在设计每个新组件和集成的时候。
关联

AuthN vs AuthZ 是一层保护;observability 和 audit log 让你能发现攻击。

§ 17

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

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

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

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

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

Logsstructured · queryableMetricsaggregatesTracesa request’s pathCorrelation ID: one request id through every hopGatewayreq-7f3aServicereq-7f3aQueuereq-7f3aWorkerreq-7f3aDownstreamreq-7f3amonitoring: something is wrong · observability: why
三大支柱——logs、metrics、traces。同一个 correlation ID 穿过每一跳,所以整条请求路径都能被还原。
ConceptObservability· 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:目标是改进系统,而不是找出罪人。

causessymptomsCPU 92%disk 70%GC pausesoften normal → noisenoisy alerts lose trusterror rate ↑latency ↑failed paymentsstale dataAlertuser painRunbookwhat to do at 3 a.m.Post-mortemblameless · fix the system
像 CPU 这样的原因往往是正常的,还会制造噪音。告警要建立在用户痛点的症状之上,后面跟着 runbook 和 blameless post-mortem。
PracticeSymptom-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 回答:我们在解决什么问题、选了什么、为什么没选其他备选、什么时候回头复查。

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

New engineer6 months laterADR-007 · one decisionContext · what problemDecision · what we choseAlternatives · why not othersConsequences · what it costsRevisit · when to re-checkNext changeknows the why
一份 ADR,一个决策:context、选择、备选方案、后果,以及何时复查。半年后,新来的人能了解“为什么”。
ToolADR· 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 里的文档,很快就会变成历史小说。

zoom in →Contextsystem + peopleContainerapps · DBs · queuesComponentmodules insideCodeclasses · rarelyLiving documentationnext to the code · updated in the PR · reviewedSequence diagramwhen the order mattersForgotten wiki→ historical fiction
C4 的四个缩放层级——通常三个就够。活文档和代码放在一起;被遗忘的 wiki 会变成小说。
ToolC4 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,安全要降低风险,财务要可预测的成本,客服要可理解的行为,工程要可维护性。

One technical riskno idempotency on the payment callbackTo engineersadd an idempotency keyTo businessmay charge twice → money + trustStakeholder map · who cares about whatProducttime-to-marketSecurityless riskFinancepredictable costSupportclear behaviourEngineeringmaintainability
同一个风险,对工程师和对业务听起来不一样。stakeholder 地图展示了每一方在乎什么。
PracticeRisk Translation· stakeholder mapping
问题
技术风险听起来像行话,业务根本听不进去。
思路
把风险翻译成对这个具体听众有意义的后果。
例子

"callback 上没有 idempotency" → "我们可能重复扣款——一个财务和声誉上的风险。"

代价
你得知道每个 stakeholder 在乎什么,并说他们的语言。
何时使用
每当一个决策取决于工程之外的人。
关联
例子是 Idempotency;业务那一侧是 NFR 和 cost of delay。

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

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

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

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

RFC · a written proposalProblemConstraintsOptionsDecisionRisksRolloutDraftarguments on paperReviewcriticism welcomeDecidebefore it’s policyDisagree & commitonly if heardnot heard ≠ commit — it’s suppression
RFC 的各个部分和决策的路径:草稿 → 带批评的 review → 决定 → disagree and commit,但前提是每个人都被听见了。
PracticeRFC· Request for Comments, disagree and commit
问题
白板前的争论,赢的是嗓门最大的,而不是最有道理的。
思路
以书面形式:问题、约束、选项、决策、风险、rollout。批评发生在决策之前。
例子
换用新的 queue 之前:一份 RFC、一周的评论,然后是决策和一份 ADR。
代价
比"开个会就定了"更慢;需要一个负责人把它推到决策。
何时使用
用于涉及多个团队、或撤销代价很高的决策。
关联
结果记入 ADR;是 Influence without Authority 的一件工具。
§ 20

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

Influence without Authority

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

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

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

BottleneckArchitectteam Ateam Bteam Cevery decision waits in lineMultiplierArchitectprinciplesboundariesquestionsTeams decidegrown by mentoring
左:架构师是瓶颈——每个决策都在排队。右:放大器——原则、边界和问题,由各团队来做决定。
ConceptInfluence without Authority· 通过提问来 mentoring
问题
架构师亲自拍板所有事,成了每个团队都要排的队。
思路
通过原则、边界和问题来施加影响,而不是下命令。
例子
不说"就这么做",而是问:"你看到了哪些备选方案,打算怎么验证风险?"
代价
当下更慢;需要花几年才能建立起来的信任。
何时使用
当你没有直属下属、但决策仍然要做的时候。
关联
通过 RFC 和 ADR 起作用;反面是 Ivory Tower Architect。

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

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

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

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

Guardrails: principles · review · ADR · gatesTeam decidesmost decisionsplatform constraints · quality gatesarchitect steps in if:irreversiblebreaks invariantssecurity riskmany teams affectedescalate
团队在护栏之内自己做决定。架构师只在右边这四个信号出现时出手。
PracticeDelegation with Guardrails· 责任仍在
问题
要么所有事都经过架构师(排队),要么没人对航向负责。
思路
团队在护栏之内自己决定;架构师对航向负责,并在明确的信号出现时出手。
例子
团队自己挑日志库;改 tenancy 模型则只能和架构师一起做。
代价
护栏要被表述清楚、写下来,并尽可能自动检查。
何时使用
当有不止一个团队、却只有一个架构师时。
关联

"不可逆"这个信号来自 Reversible vs Irreversible;护栏就是 ADR 和 quality gate。

§ 21

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

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

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

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

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

PerformanceAvailabilitySecurityComplianceOperabilityBusiness impactthe architect translateslost revenueregulatory risksupport costspeed to marketCost of delay: waiting has a price, not only building
左:non-functional requirements。右:它们对业务意味着什么。架构师是中间的翻译。
ConceptNon-functional Requirements· NFR, cost of delay
问题
系统质量被当成"技术上的想要",一再被推后。
思路
NFR 是产品属性;用收入、风险和成本来讨论它们。
例子
"结账能撑过黑色星期五"是钱,不是细节。
代价
你得用数字估算影响,哪怕只是粗略的。
何时使用
当架构工作和功能争抢时间的时候。
关联

用数字表达:SLO;被推后的工作:Technical Debt;沟通的语言:Risk Translation。

Build vs Buy — 不是面子问题

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

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

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

Build · control + differentiationcore domainwhat customers pay you forBuy · speed + less opsauthpaymentsemailmonitoringVendor lock-in → ask about the exit costif the vendor changes price, API or quality — how hard is it to leave?
自建客户为之付钱的核心;购买通用的部分。对所有买来的东西,关键问题是退出成本。
ConceptBuild vs Buy· capability map, vendor lock-in
问题
有人出于面子自己造,有人什么都买、却没有退出计划。
思路
自建能让业务差异化的部分;购买通用的部分。对所有买来的东西,估算退出成本。
例子
你自己的 domain 引擎——自建;邮件发送或支付——通常购买。
代价
自建:时间,以及永久的维护。购买:依赖供应商的价格、API 和质量。
何时使用
每次说"我们自己写一个"之前。
关联
陷阱是 NIH 综合症;Dependency Inversion 有助于隔离供应商。
§ 22

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

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

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

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

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

Specbehaviourboundariesinvariantstestsacceptance criteriavague spec → bad code fasterAI agentfast executorCode + teststhe diffArchitect reviewagainst the specfix the spec, not only the code
spec 是主要的产物。agent 很快把它变成代码;架构师拿代码对照 spec 检查,不符时去修 spec。
PracticeSpecification-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"的地方,而是有强规格说明、小步迭代、自动化检查和架构负责人的地方。

Agent PRcompiles ≠ correctQuality gatestestsscenario evals · not just happy pathimport boundariestype checkssecurity reviewMergeowned
agent 的 PR 要过和人一样的 quality gate:测试、scenario eval、import 边界、类型、安全 review。
PracticeAgent Output Review· 给 AI 代码的 quality gate
问题
agent 的代码因为能编译、看起来很自信,就被接受了。
思路
和对人一样的自动化 quality gate,一样的 review。
例子
Scenario eval 能发现 agent 只处理了 happy path,忘了重复支付的情形。
代价
自动化检查和 import 边界要提前搭好。
何时使用
agent 的每一个 PR,没有例外。
关联

检查与 Specification-driven Development 的一致性;边界来自 Dependency Inversion。

§ 23

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

Ivory Tower Architect

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

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

Ivory towerpretty diagramsCureread PRs · watch incidents · talk · walk the pathCode / PRsIncidentsEngineersgapdecisions made far from code, operations and the team
塔离地面很远:离代码、incident 和工程师都很远。解药是走下来——读 PR、看 incident、聊天、走一遍改动的路径。
Anti-patternIvory 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(技术完美主义)。那个从未到达用户手里的完美架构,会输给一个足够好、能交付、还能演进的系统。

The real problemstart hereResume-driven devtech for the CVCargo cultcopy the form, not the reasonNIH syndromebuild what you should buyPerfectionismperfect, never shipped
四个把你从真实问题上拉开的陷阱:为简历选技术、照搬形式、“我们自己造”,以及无休止的打磨。
Anti-patternResume-driven Development· cargo cult, NIH, 完美主义
症状
决策的理由是时髦、别人的例子或美感——而不是某个问题。
为什么有害
你付出了复杂技术的代价,却没得到它的好处,或者压根没能到达用户。
例子
三个人的团队上 Kubernetes 和 microservices,"因为 Netflix 就这么干"。
解药
从问题出发,诚实地说出每个选项的代价。
关联

靠 Trade-offs 和 Build vs Buy 来治;常常催生 Distributed Monolith。

§ 24

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

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

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

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

New techthe hypewhat problem does it solve?what price does it add?where has it already failed?what are the alternatives?what must be true to help?Adoptit fitsSkip / waitnot yet
对任何新技术的五个过滤问题。问完之后,你再决定:现在就用,还是再等等。

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

T-shapedbreadth: neighbouring areasone deep areaM-shapedbreadthdatadistributionsecurityproductAI toolsa mature architect grows several deep pillars
T:一个深的领域加上广度。M:成熟架构师身上的几根深支柱。
ConceptT-shaped / M-shaped Skills· 从系统中学习,而非 hype
问题
人们学遍每一项时髦的技术,却没获得深度和判断力。
思路
通过"问题与代价"的提问去学技术;培养几根深的支柱。
例子

一个新数据库:它解决什么问题、已经在哪里失败过、要满足什么条件它才对我们有帮助。

代价
比读发布公告慢;深度要花好几年。
何时使用
雷达上每出现一项新技术的时候。
关联
防范 Resume-driven Development 的手段;这些支柱就是本课程的各个主题。

Learning in Public — 加速成熟

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

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

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

Decideon a real systemWrite it downADR · RFC · notesFeedbackpost-mortems · peersBetter questionsfor you and the teamgaps show up before production
循环:决定 → 写下来 → 获得反馈 → 问出更好的问题。你思维里的漏洞,会在生产环境之前暴露。
PracticeLearning in Public· 用文字学习
问题
生产环境会比你的同事更早发现你推理里的缺陷。
思路
把决策和复盘写下来,分享出去,从反馈中学习。
例子
ADR、RFC、工程笔记、post-mortem、对决策的简短复盘。
代价
写作的时间,以及展示未完成想法的勇气。
何时使用
一直——架构师就是这样成长的。
关联

用到 ADR、RFC 和 post-mortem;和 Influence without Authority 一样,让团队更强。

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

课程地图:一切如何相连

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

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

Mindsettrade-offs · debt · cognitive loadStructureboundaries · contractsData & distributiontruth · consistencyQualitylatency · scale · SLOshapesDocs & talkADR · C4 · RFCLead & businessinfluence · NFR · buySecurity & opsauthz · threats · alertsrecorded inverified byconvinceriskAI-native & growthspecs · review · anti-patterns · skillsscales into
课程的八组概念及其关联:从思维到结构、数据和质量,再到文档、业务、运维和成长。

速查表:本课程的全部 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 ContextDDD 的边界

一条边界,边界内每个词只有一种含义。

Dependency Inversion依赖指向内部

业务逻辑依赖抽象;细节从外部接入(ports and adapters)。

Abstraction为吸收变化而存在

一层的价值在于吸收变化、让你无需外部世界就能测试,而不是看起来漂亮。

API Contractcontract 高于传输方式

schema、错误、versioning 和兼容性,比选 REST、GraphQL 还是 gRPC 更重要。

Idempotency重复也安全

重复一次操作,得到相同的最终效果。防范 retry 和重复。

Modular Monolith一次部署,坚固的墙

一个有严格内部边界的应用。往往是最好的起点。

Distributed Monolith两头最糟的结合

只能一起发布的服务。付出了分布式的成本,却没有独立性。

Strangler Fig一块一块迁移

让新系统围绕旧系统生长,逐步把流量迁过去,而不是一次性推倒重写。

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

Data Model数据比代码活得久

最贵的决策:规范化、OLTP vs OLAP。

Event Sourcingevent 的 log 才是真相

状态由 event 重建:完整的历史和审计,但运行和演进都很贵。

CQRS分开写模型和读模型

命令和查询各有自己的模型;当读和写差别很大时有用。

Single Source of Truth一个权威版本

对每个事实,你都知道主版本住在哪里;其余的都是派生的副本。

Cache Invalidation会撒谎的第二份真相

用新鲜度换速度;难的问题是什么时候失效。

Optimistic vs Pessimistic Locking写时检查 vs 先加锁

防止并发编辑互相覆盖的两种方式。

CAP / PACELC网络就是不确定性

出现 partition 时:consistency 或 availability;否则:latency 或 consistency。

Consistency Modelsstrong, eventual, causal

不是最严格的模型,而是足以应对风险的那个。

Saga带补偿的一串步骤

跨服务的长事务,每一步都有对应的撤销,而不是一把全局锁。

Queue vs Pub/Sub

Async 在时间上解耦,但带来延迟、重复和顺序问题。

At-least-once Deliveryexactly-once 是营销话术

一条消息可能到两次;consumer 必须是 idempotent 的。DLQ、backpressure。

系统质量(13–15)

Tail Latencyp95 / p99

用户感受到的是长尾,而不是平均值。

Profiling先度量

只优化你找到的热点;Amdahl 和 Little 划定了界限。

Vertical vs Horizontal Scalingscale up / scale out

一台大机器,或者 load balancer 后面许多完全相同的机器。

Scale CubeX / Y / Z

克隆、按功能拆分、按数据拆分。

Retry + Circuit Breaker + Bulkhead韧性

带 backoff 和 jitter 的 retry、对付已死依赖的 breaker、相互隔离的池。

SLI / SLO / SLAerror budget

度量、目标和承诺;error budget 决定是做功能还是保可靠性。

安全与运维(16–17)

Authentication vs AuthorizationAuthN / AuthZ

"你是谁?"和"你能做什么?"是两个不同的步骤。

Threat ModelingSTRIDE, defense in depth

在攻击之前找出威胁,并分层防御。

Observabilitylogs · metrics · traces

系统能解释"为什么";correlation ID 把整条路径串起来。

Symptom-based Alertingrunbook, post-mortem

按用户的痛点告警;runbook 说明该做什么;post-mortem 是 blameless 的。

文档与沟通(18–19)

ADRArchitecture Decision Record

context、决策、备选方案、后果——每个决策一份。

C4 Model活文档

按缩放层级画、和代码放在一起的图。

Risk Translationstakeholder mapping

同一个风险,对工程师和对业务用不同的话说。

RFCdisagree and commit

一份书面提案,批评发生在决策之前。

领导力与业务(20–21)

Influence without Authoritymentoring

用原则、边界和问题,代替命令。

Delegation with Guardrails责任仍在

团队做决定;遇到不可逆、invariant、安全和跨多团队影响时,架构师出手。

Non-functional RequirementsNFR, cost of delay

用收入、风险和成本来表达的产品属性。

Build vs Buycapability map

自建核心,购买通用部分,清楚退出成本。

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

Specification-driven Development把 spec 当作产物

执行者越快,spec 的精确度就越重要。

Agent Output Reviewquality gate

agent 的代码要过和人写的 PR 同样的检查。

Ivory Tower Architect站在山上做架构

在远离代码、运维和团队的地方做出的决策。

Resume-driven Developmentcargo cult, NIH, 完美主义

为简历选技术、照搬形式、自己造本该买的东西、无休止的打磨。

T-shaped / M-shaped Skills从系统中学习

深度加广度;成熟的架构师有几根深的支柱。

Learning in Public用文字学习

把决策写下来,在生产环境之前看到你思维里的漏洞。

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

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

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