完整课程 · No. 03
一门面向已经在写代码、但想学会做架构决策、向团队和业务解释这些决策、并看见系统全貌的工程师的课程。
24 个模块 · 52 个概念 · 每个都配一张图 · 结尾有速查表
架构师和 senior engineer 的区别,不在于他懂更多技术。他为"决策在时间维度上的后果"负责。
课程地图 — 六个部分,24 个模块
一张地铁线路图:先一眼看到所有线路,再一站一站地坐过去。
课程从思维走到实践。先讲架构师怎么思考(mindset)。再讲系统内部怎么搭(structure),以及系统分散到网络上之后会变成什么样(distribution)。接着是质量属性(quality attributes)、和人打交道(people),最后是这个职业的走向(future)。
怎么读:每个概念都有它的英文术语(查文档、面试时听到的就是它)、一个日常画面、一张图,以及一张带"是什么 / 例子 / 代价 / 关联"几栏的卡片。课程结尾有一份速查表,把所有概念放在一页里。
Long-term thinking — 架构师以年为单位思考
工程师造一座明天就有车开过去的桥。架构师则问:等卡车开始上桥会怎样、等隔壁起了一个新城区会怎样、等维修费比桥本身还贵会怎样。
工程师通常解决被提出来的那个问题。架构师则先检查这个问题有没有被提对。这不是清高,也不是想"聊点高深的"。只不过一个架构决策,通常比一行代码更难改、改起来更贵。
脑子里最关键的那个转变:你不再只问"我该怎么做 X",而开始问**"我们为什么需要 X、有哪些备选、会失去什么、三年后它会是什么样"**。
- 是什么
- 评判一个决策时,不只看"明天能不能用",还看它在一到三年里的表现。
- 怎么做
- 先问"为什么要 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。
- 是什么
- 按撤销的代价给决策分类:几天或几周,还是几个月或几年。
- 怎么做
可逆的:快速决定,别争太久。不可逆的:慢慢来,摆出备选方案,并留下书面记录。
- 例子
- 日志库是可逆的。tenancy 模型或 API contract 几乎不可逆。
- 注意
- 在一个可逆的细节上争论一周,和一小时就定下一个不可逆的决策,一样糟。
- 关联
- Data model、API contract、service boundaries。
- 我们真正在解决的是什么问题?
- 如果负载涨 10 倍,什么会变?
- 这里哪些决策可逆,哪些几乎不可逆?
- 一年后谁来维护这套东西?
架构师不造"最正确的系统"——他造一个能在现实里存活下来的系统。
架构里没有免费的决策。每个选择都用一个属性的代价,买来另一个属性。
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 的运维复杂度。"专业的语言,听起来就是这样。
- 是什么
- 用一个属性的代价买来另一个属性的选择——而且要说出口。
- 怎么做
- 说清楚得到什么、失去什么,以及为什么现在这个价格可以接受。
- 例子
"按 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:系统已经撞上了上限,而架构没给增长留余地。架构师的工作不是选一个极端,而是守住中间。
- 是什么
- 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?
技术债并不总是坏事。坏的是那种意外背上、没有记录、也没打算还的债。
Technical debt — 是贷款,不是垃圾
为买设备贷款,能让生意提速。为办派对贷款,只会留下宿醉和利息。
**Technical debt(技术债)**是一个"现在让我们更快、却在将来产生修改成本"的决策。有时这是对的:要试水市场、要赶截止日期、要留住客户。问题出在团队把所有东西都贴上"债"的标签:烂代码、缺测试、混乱的依赖、过时的库。
一个有用的模型是 Martin Fowler 的 Technical Debt Quadrant:deliberate / inadvertent(有意 / 无意),prudent / reckless(审慎 / 鲁莽)。有意且审慎的债:"我们暂时不做 multi-region,因为只有 200 个用户、一个市场。"无意且鲁莽的债:"我们不知道自己需要 transaction。"
- 是什么
- 一个现在让我们更快、却在将来产生修改成本的决策。
- 怎么做
- 有意地背,记下是什么、为什么,并设一个回头复查的触发条件。
- 例子
- "暂时不做 multi-region:200 个用户,一个市场"——有意且审慎。
- 注意
- 如果什么都叫债——烂代码、没测试、旧的库——这个词就什么也不表示了。
- 关联
- Debt interest、ADR(记录决策)、trade-off。
Debt interest — 债是要付利息的
路坏了,每趟出行都多花五分钟。一个月后它就不再是个坑,而是对整座城市征收的一笔税。
技术债的利息,就是之后每一次改动都变慢。团队越常碰某个区域,这笔债就越贵。所以热点 domain 里的债,比一个很少用的后台管理页面里的债更危险。
架构师要谈出一套还债策略:到底什么算债、它带来什么风险、什么时候回头复查决策、什么信号说明"该还了"。没有这些,"技术债"就成了一种情绪化的争论,而不是一件被管理的工具。
- 是什么
- 带着债的代码里,之后每一次改动都变慢。
- 怎么做
- 利息 ≈ 变慢的程度 × 团队碰这块区域的频率。
- 例子
- 每周都在改的支付里的债,比一个每季度才打开一次的后台管理页面里的债更贵。
- 注意
- 没有还债策略,"技术债"就成了一种情绪化的争论。
- 关联
- Technical debt、cognitive load。
- 我们为什么做了这个妥协,记在哪里了?
- 每个 sprint 我们在付多少利息?
- 要发生什么,这笔债才会变成阻塞?
- 有还债计划吗?至少有回头复查的触发条件吗?
一个没人能装进脑子里的系统,即使有很强的工程师也会出问题。
Cognitive load — 架构必须装得进一个人的脑子
地铁线路图有用,不是因为它画出了隧道里的每颗螺丝。它画的是线路、换乘和行进方向。这就够做决定了。
**Cognitive load(认知负荷)**是系统要求工程师付出的脑力。有 intrinsic load:domain 本身的复杂度。也有 extraneous load:由糟糕的架构、不明显的联系、偶然的例外和"魔法"带来的额外复杂度。架构师消除不了业务本身的复杂度,但有义务降低偶然的复杂度。
好的模块可以单独理解。好的 bounded context 有自己的语言和自己的 invariant。好的团队拥有一块能装进自己脑子里的领域。
- 是什么
- 工程师要安全地改动某处,得在脑子里装下多少 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 的时髦词——它是一件架构工具。有时,正确拆分系统要从正确拆分职责开始。
- 是什么
- 系统会映射出构建它的组织的沟通结构。
- 怎么做
- 让团队的边界和系统的边界对齐:每个团队拥有自己的领域。
- 例子
- 三个互不沟通的部门,交付的是"同一个菜单里的三个产品"。
- 注意
- 边界对不上,团队就会不断踩到彼此的地盘。
- 关联
Team Topologies(stream-aligned、platform、enabling、complicated-subsystem)、bounded context。
只有当你能沿着正确的边界切开,一个大系统才变得可控。
Cohesion & coupling — 按层切、按 domain 切,还是按数据流切
屠夫不会随便乱剁一头牲畜——他沿着关节下刀。好的拆分也在找自然的断点。
拆分不是"分到不同文件夹"。它是选择责任在哪里结束。**High cohesion(高内聚)**意味着组件里的一切都围绕同一件事。**Low coupling(低耦合)**意味着组件对其他组件知道得很少。
横向切得到 layers(层):UI、application、domain、infrastructure。纵向切得到 feature 或 bounded context:billing、identity、catalog、comments。真实系统两种都需要,但把一种当成另一种,很危险。
- 是什么
组件内部的一切都围绕同一件事(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 里的"Customer"和 support 里的"Customer"是不同的模型。
- 代价
- 要在边界处做模型映射;context 之间要做转换。
- 何时使用
- 当同一个概念,对业务的不同部分意味着不同的东西。
- 关联
- Cohesion / coupling、Conway's Law、microservices。
业务逻辑应该依赖业务规则,而不是框架、数据库或 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。
- 问题
- 业务规则已经长进了框架和数据库——没法测试,也没法替换。
- 思路
- 高层逻辑依赖抽象;具体实现从外部接入。
- 例子
service 接收一个
PaymentGatewayProtocol,而不是自己构造StripeClient。- 代价
- 多出更多接口和文件。
- 何时使用
- 有真正的业务规则,需要比技术更替活得更久。
- 关联
- Clean / Hexagonal / Onion Architecture、ports and adapters。
Abstraction — 为吸收变化而存在,不是为了好看
adapter 该放在插头真正不一样的地方。如果你只因为喜欢 adapter,就在每个插座上都装一个,房子反而更不安全。
不是每个类都需要接口。但凡是碰到外部世界、时间、随机性、网络、文件系统或数据库的东西,在测试里都应该能被替换。否则你测的就不是业务逻辑——而是在单元测试里启动了一个小型的生产系统。
一层的代价是更多文件、更多字。回报是可测试性、可替换的基础设施、清晰的边界。如果没有回报,这一层就是累赘。
- 是什么
- 接口放在实现真的会变、或会妨碍测试的地方。
- 怎么做
时间、随机性、网络、文件、数据库 → 放在 port 后面;测试里用 fake,生产里用真实的 adapter。
- 例子
- 一条业务规则不需要数据库、HTTP 或真实时间就能测——只要几毫秒。
- 注意
- 没有回报的一层,只是多余的文件和字,不是架构。
- 关联
- DIP、testability、over-engineering。
API 是一份承诺。毁约很容易;重建客户端的信任很难。
API contract — contract 比传输方式更重要
护照检查不在乎门是什么牌子。它在乎的是接受哪些证件、适用哪些规则、出问题时会发生什么。
REST、GraphQL、gRPC 和 WebSocket 是不同的沟通方式。REST 适合面向资源的 API 和简单的缓存。GraphQL 在不同客户端需要不同形状的读取时有帮助。gRPC 适合有类型的服务间通信。WebSocket 用于实时的双向事件。
选哪种传输方式是次要的。重要的是 API contract:schema、错误、idempotency、versioning、backward compatibility、deprecation policy。
- 是什么
一份承诺:schema、错误、idempotency、版本、兼容性,以及淘汰旧东西的规则。
- 怎么做
- 先谈妥 contract,再为这个任务挑传输方式。
- 例子
REST 用于资源和缓存;GraphQL 用于多变的读取;gRPC 用于有类型的服务间通信;WebSocket 用于实时。
- 注意
- 毁约很容易;重建客户端的信任很难。
- 关联
- Idempotency、versioning、reversible vs irreversible。
Idempotency — 它让你在真实世界里不翻车
你按十次电梯按钮,电梯不该来了又走十次。它应该明白意图只有一个。
**Idempotency(幂等)**是指重复一次操作,产生相同的最终效果。在联网的系统里,重试不可避免:超时、retry、重复的 webhook、worker 重启。如果创建支付、订单或消息不是幂等的,系统迟早会多做点什么。
架构师在 contract 层面设计幂等:idempotency key、unique constraint、安全的重试、清晰的状态。
- 是什么
- 重复一次操作,得到与第一次调用相同的最终效果。
- 怎么做
- 请求里带 idempotency key + 数据库里的 unique constraint + 清晰的状态。
- 例子
- 支付超时 → 客户端用同一个 key 重发
POST /payments→ 只扣一次款。 - 注意
- 没有它,重复(retry、重复的 webhook、worker 重启)迟早会多做点什么。
- 关联
- At-least-once delivery、API contract、retries。
代码会被重写。数据要迁移好几年。所以数据模型是最贵的架构决策之一。
Data model — 数据比应用活得更久
外墙一周就能重刷。地基浇错了,会在整栋楼的寿命里一直提醒你。
语言、框架、UI 可能换好几轮。但表、event、key、关系和历史记录会留下来。数据模型里的一个错误,往往会变成产品每个未来版本里的错误。
**Normalization(规范化)**减少重复和数据不一致的风险。**Denormalization(反规范化)**加快读取,并简化 read 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,是把一个问题变成两个问题的昂贵做法。
- 问题
- 当前状态无法说明我们是怎么走到这一步的。
- 思路
- 把变更存成 event log;状态 = 把它们重放一遍。
- 例子
- 银行对账单:余额是各笔操作之和。
- 代价
event schema 的演进、更难的查询、eventual consistency、要求很高的工具链。
- 何时使用
- 历史、审计、状态恢复和 replay 很重要的时候。
- 关联
- 常与 CQRS 搭配。
- 问题
- 一个模型既服务不好写入,也服务不好复杂的读取。
- 思路
- 把写模型和读模型分开。
- 例子
- 订单写进规范化的模型;报表页面读一份现成的视图。
- 代价
- 两个模型之间的同步和延迟。
- 何时使用
- 当读取确实复杂的时候——而不是因为"听起来很有架构感"。
- 关联
- Event sourcing、denormalization、eventual consistency。
状态是住在某个地方的复杂度。真相的副本越多,出错的方式就越多。
Single source of truth — 不等于只有一个数据库
一家公司可以有很多报表,但必须只有一份官方的工资名册。否则发薪日就成了一场闹剧。
Single source of truth 的意思是:对每一个重要的事实,你都知道权威版本住在哪里。缓存、搜索索引、read replica、materialized view——这些都是派生出来的副本。它们可以很有用,但不该假装自己是原始的真相。
**Stateful(有状态)**组件持有状态,需要小心对待:事务、锁、恢复、备份。**Stateless(无状态)**组件更容易扩展和替换,但它们依然依赖住在别处的状态。
- 是什么
- 对每一个重要的事实,都清楚它的权威版本住在哪里。
- 怎么做
- 一份主记录;缓存、索引、replica 和 view 都是派生的副本。
- 例子
- 报表有很多份,但官方的工资名册只有一份。
- 注意
- 它不是"所有东西用一个数据库"——而是"每个事实只有一个所有者"。
- 关联
- Stateful / stateless、cache、CQRS。
Cache 与 locking — 偶尔会撒谎的第二份真相
冰箱上贴的日程很方便,直到有人在手机日历里把会议改了时间。
**Cache(缓存)**用新鲜度换速度。缓存最难的问题不是"怎么放进去",而是"什么时候失效"。TTL、explicit invalidation、write-through、write-behind、cache-aside——这些是在复杂度、新鲜度和负载之间的不同折中。
当两个操作争抢同一份状态时,就会出现 race condition。Optimistic locking 说:"冲突很少,我们在写入时检查版本。"Pessimistic locking 说:"冲突很危险,我们事先就加锁。"两者在不同条件下都是对的。
- 是什么
- 把数据的一份副本放到离读取者更近的地方:用新鲜度换速度。
- 怎么做
TTL、explicit invalidation、write-through、write-behind、cache-aside——不同的折中。
- 例子
- 冰箱上的日程 vs 手机上的日历。
- 注意
- 主要的问题不是"怎么放进去",而是"什么时候扔掉"。
- 关联
- Single source of truth、eventual consistency。
- 是什么
- 防止两个操作搞坏共享状态(race condition)的两种方式。
- 怎么做
- Optimistic:写入时检查版本。Pessimistic:事先加锁。
- 例子
- 两个经理编辑同一条记录:第二个会收到冲突,并重新读取数据。
- 注意
- Optimistic 在冲突频繁时吃力;pessimistic 在锁竞争严重时吃力。
- 关联
- Transactions、stateful components。
分布式解决规模的问题,却带来了时间、顺序和网络信任的问题。
CAP 与 PACELC — 网络是不确定性的来源
monolith 是一间屋子里的对话。分布式系统则是身处不同时区的人之间的通信——信有时会丢,会到两次,或者迟到。
在分布式系统里,你不能只是"调用一个函数"。服务之间隔着网络、timeout、retry、partial failure、不同的时钟、不同版本的代码。在 monolith 里原本是本地事务的东西,变成了一套协调协议。
CAP theorem 常被糟糕地概括。实践中有用的版本是:在 network partition 期间,你要在 consistency 和 availability 之间二选一。PACELC 补充说:即使没有 partition,你依然要在 latency 和 consistency 之间选。
- 是什么
- 出现 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 变得可见。
Saga pattern 把一个长的业务事务拆成若干步骤,并配上 compensating actions(补偿动作)。Two-phase commit 能提供跨资源的原子性,但在真实的分布式系统里既昂贵又脆弱。架构师挑的不是"最严格"的模型,而是足以应对风险的那个。
- 是什么
- 所有人以多快的速度、按什么顺序看到同一份真相。
- 怎么做
- Strong:永远新鲜。Causal:保持因果顺序。Eventual:稍后追平。
- 例子
- 共享文档:"几乎瞬间"就行。银行转账:不行。
- 注意
- Strong 付出的是 latency 和 availability;eventual 付出的是暂时的不一致。
- 关联
- CAP / PACELC、cache、CQRS。
- 问题
- 一个业务操作横跨多个服务;没有共享的事务。
- 思路
- 一串步骤;如果某一步失败,补偿动作撤销已经做过的部分。
- 例子
- 预留库存 → 扣款 → 发货失败 → 退款 → 释放预留。
- 代价
- 每一步都需要一个补偿;中间状态是可见的。
- 何时使用
- 用来替代 two-phase commit——后者跨网络既昂贵又脆弱。
- 关联
- Eventual consistency、idempotency、microservices。
微服务不会修好糟糕的设计。它们只是把本地的问题变成分布式的问题。
Modular monolith — 常被低估的起点
一座组织得好的仓库,比十座小仓库更快——如果反正是同一个快递员在它们之间来回跑。
monolith 不是一句骂人话。糟糕的 monolith 是一团乱糟糟的依赖。好的 modular monolith 有严格的内部边界、分开的 domain、明确的接口,以及单一的 deployment unit。对小团队来说,这往往是最好的起点。
Microservices 在你需要独立发布、自治的团队、不同的扩展特征、故障隔离,或不同技术栈时有帮助。代价是:网络调用、observability、分布式事务、DevOps 开销、versioning、服务之间的安全。
- 问题
- monolith 变成了一团乱糟糟的依赖。
- 思路
- 严格的内部边界和明确的接口——但仍是一个 deployment unit。
- 例子
- billing、identity、catalog 模块在同一个应用里,通过模块 API 互相通信。
- 代价
- 需要对边界保持纪律;作为一个整体扩展和发布。
- 何时使用
小团队、早期产品。Microservices——当你需要独立发布、自治的团队、不同的负载特征、故障隔离时。
- 关联
- Bounded context、microservices、Strangler Fig。
Distributed monolith 与 Strangler Fig — 两头最糟的结合,以及出路
就像住在各自独立的公寓里,却每拿一把勺子都要跑去隔壁。
Distributed monolith 看起来像 microservices,但服务并不自治:共享的数据库 schema、同步调用链、只能一起发布的版本、牵一发动全身的改动。团队付出了分布式的成本,却没得到独立性。
Strangler Fig pattern 让你能小心地迁移:把旧系统包起来,逐步抽出能力,沿着真实的业务边界切,而不是沿着表去切。
- 迹象
- 共享的数据库 schema、同步调用链、只能一起发布、每个改动都牵连所有人。
- 为什么有害
- 你为分布式付了钱,却没得到独立性。
- 例子
- 各自独立的公寓,每拿一把勺子都要去隔壁。
- 出路
每个服务拥有自己的数据、异步连接、按业务含义划分边界——或者退回到 modular monolith。
- 关联
- Modular monolith、bounded context、sync vs async。
- 问题
- 旧系统没法一次性重写。
- 思路
- 用 facade 把它包起来,逐步把能力迁到新服务里。
- 例子
- 先抽出 billing,再抽 catalog——由 facade 来路由请求。
- 代价
- 两套系统会并存很长一段时间。
- 何时使用
- 迁移一个在线的系统,又不想"推倒重来"。
- 关联
- 沿着 bounded contexts 切,而不是沿着表切。
Sync 在时间上把系统耦合在一起。Async 打破这种耦合,但带来延迟、重复和顺序的复杂性。
打电话要求双方此刻都有空。信可以稍后再读——但它可能丢、可能迟到,或者要求回复。
**同步(sync)**调用容易理解:发出去,等回答。但如果接收方慢或者不可用,受苦的是发送方。通过 queue 或 pub/sub 做 async,能让你扛过故障和负载尖峰,但它要求另一套思维模型。
queue 通常把工作分给多个 consumer。pub/sub 则把一个 event 广播给许多订阅者。Kafka、RabbitMQ、SQS、Redis Streams——不同的工具,有不同的顺序、持久性、replay 和运维模型。
- 问题
- 同步调用在时间上把系统耦合起来:接收方挂了,发送方受苦。
- 思路
- 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 是"接收方跟不上了"的信号。没有它,系统会优雅地接收一切,然后在内部死掉。
- 是什么
- 保证消息一定会到,但可能不止一次。
- 怎么做
idempotent consumer 丢弃重复项;dead letter queue 在 N 次失败后收集消息;backpressure 说"我跟不上了"。
- 例子
- 一条重复的"发工资"消息,不会把工资发两次。
- 注意
"Exactly-once"几乎总是营销话术;没有 backpressure,系统会接收一切,然后在内部死掉。
- 关联
- Idempotency、queue / pub/sub、reliability。
"让它快一点"不是需求。需求听起来像 latency、throughput、percentile 和负载特征。
Tail Latency — 平均响应时间几乎总是在撒谎
如果九个人一分钟内拿到了餐,第十个人等了一个小时,平均值看起来还能忍。第十个人不这么认为。
这就是 tail latency 的概念。先说三个词。Latency 是单次操作的延迟。Throughput 是系统在单位时间内处理多少次操作。Percentile 展示分布:p50 是中位数,p95 表示 95% 的请求比这个值快,p99 是长尾。
用户感受不到平均值——他们感受到的是长尾。当一个页面依赖好几个后端调用时,tail latency 尤其重要:慢响应的概率会叠加起来。
- 问题
- 平均响应时间看起来没问题,而有些用户等了非常久。
- 思路
- 看 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 越大,系统内部积压的在途工作就越多。
- 问题
- 团队"加速"了错的地方:花了一周,系统并没有更快。
- 思路
- profile 显示时间真正花在哪里。只修热点。
- 例子
"慢页面"原来是一个 N+1 查询,而不是语言慢——用一条查询加一个 join 就解决了。
- 代价
- 度量和工具要花时间;你需要一个可复现的负载特征。
- 何时使用
- 任何优化之前。没有 profile,就不优化。
- 关联
Amdahl's Law 是加速比的天花板;Little's Law 把 latency、throughput 和队列联系起来。
可扩展性,是在负载和功能上增长,而不必从头重建系统的能力。
Vertical vs Horizontal Scaling — 它们买到的东西不同
你可以买一辆大卡车。也可以买十辆小的。前者更简单,后者更灵活——但需要一个调度员。
Vertical scaling(scale up)是把一台机器变强。在还有余量时很简单。Horizontal scaling(scale out)是增加更多实例。更灵活,但需要 stateless 的应用层、load balancing、一套共享状态的策略和 observability。
数据库比应用更难扩展。Read replica 帮的是读,不是写。Sharding 能拆分数据,但迫使你选一个 shard key。糟糕的 shard key 会把扩展变成负载失衡。
- 问题
- 负载在增长,一台机器很快就不够用了。
- 思路
- 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 分区。
- 问题
大家只想着流量,但系统其实死于功能增长:所有东西都住在一个 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。
可靠的系统不是什么都不会坏的系统。可靠的系统知道坏了该怎么办。
Retry、Circuit Breaker、Bulkhead — 没有策略的 retry 是对自己的攻击
门打不开,敲一次是合理的。每秒敲一千次,就成了门和邻居的麻烦。
Retry 用于瞬时的故障。但 retry 需要上限、timeout、exponential backoff 和 jitter。否则,局部故障就会变成 retry storm。
Circuit breaker 会暂时停止对一个卡住的依赖发起调用。Bulkhead pattern 隔离资源:如果某个 downstream 挂了,它不会把你所有的 worker 线程和连接池一起拖下水。
- 问题
- 一个依赖变慢或挂了;盲目的 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 告诉你,你被允许花掉多少不可靠性。如果预算烧光了,团队就停止追功能,转而修可靠性。这是用业务语言讲出来的工程质量。
- 问题
- 没人度量过"可靠",所以功能 vs 质量的争论永远没完。
- 思路
度量(SLI),约定目标(SLO),把对客户的承诺单独放着(SLA)。离 100% 的差距就是 error budget。
- 例子
- SLO:30 天内 99.9% 的请求成功。预算烧光——功能暂停。
- 代价
- 你得诚实地选择度量什么,并在预算用完时守住这条规则。
- 何时使用
- 当需要用同一种语言和业务讨论可靠性的时候。
- 关联
SLI 常建立在 Tail Latency 之上;告警针对症状触发;它是用数字表达的 NFR。
你不能在发布前夕才加上安全。它必须内建在访问模型、数据、基础设施和流程里。
Authentication vs Authorization — 是两回事
护照证明你是谁。车票证明你可以去哪里。一个不能代替另一个。
Authentication(AuthN)回答"这是谁?"。Authorization(AuthZ)回答"他被允许做什么?"。把两者搞混很危险:一个用户可能正确地登录了,却依然无权查看别人的发票。
RBAC 通过角色授予权限。ABAC 考虑属性:资源所有者、tenant、地区、对象状态。基于 session 的 auth 更容易撤销;JWT 无需数据库就更容易验证,但更难撤销。选哪个取决于威胁和生命周期。
- 问题
- "用户已登录"被当成"用户被允许"——结果他们读到了别人的数据。
- 思路
- 两个独立的步骤: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,并设置多层保护,而不是一把"主"锁。
- 例子
泄漏的一把密钥:它能打开什么(blast radius)、你多快能轮换它、它在审计里出现在哪。
- 代价
- review 要花时间,secrets management 要有纪律。
- 何时使用
- 在设计每个新组件和集成的时候。
- 关联
AuthN vs AuthZ 是一层保护;observability 和 audit log 让你能发现攻击。
你修不了你看不见的东西。系统必须能解释自己的状态,而不需要在代码里考古。
Observability — monitoring 告诉你哪里不对,observability 帮你理解为什么
"check engine"灯很有用,但修理工仍然需要仪表、历史记录和诊断工具。
Monitoring 回答"有地方不对了"。Observability 回答"为什么"。observability 的三大支柱:logs、metrics、traces。Structured logging 让日志可以被查询。Metrics 展示聚合数据。Distributed tracing 跟踪一个请求穿过各个服务的路径。
Correlation ID 是个能救命的小东西:同一个请求 ID 贯穿 gateway、服务、queue、worker 和 downstream。
- 问题
- 夜里出了故障,却要靠在代码里考古来找原因。
- 思路
- 系统自己解释自己的状态:结构化的日志、聚合的指标、请求路径的 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:目标是改进系统,而不是找出罪人。
- 问题
- 告警按原因触发,团队习惯了噪音,结果漏掉了真正的故障。
- 思路
- 只有用户受苦时才叫醒人;每条告警都配一份 runbook。
- 例子
- 不是"CPU 92%",而是"支付失败数上升"——再配一份 runbook:先查什么。
- 代价
- 你得知道自己的症状有哪些,并让 runbook 保持最新。
- 何时使用
- 用于所有值班工程师要在夜里处理的事。
- 关联
- 症状就是 SLI;post-mortem 和 ADR、RFC 属于同一种书面文化。
一个没有记录下来的架构决策,会随着它作者的 context 一起消失。
ADR — Architecture Decision Record 胜过记忆
口头约定,在第一次休假、第一次离职或半年后的第一次争论之前都管用。
Architecture Decision Record 记录 context、决策、备选方案和后果。ADR 不必是一部小说。好的 ADR 回答:我们在解决什么问题、选了什么、为什么没选其他备选、什么时候回头复查。
文档不是为了审计——而是为了下一次改动。新来的工程师需要理解的不只是"怎么做",还有"为什么"。
- 问题
- 决策只存在于作者的脑子里;作者走了,没人知道当初为什么这么做。
- 思路
- 每个决策一份简短的记录: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 里的文档,很快就会变成历史小说。
- 问题
- 图是为了好看而画的:所有东西挤在一张图里,一个月后它就在撒谎。
- 思路
- 按缩放层级来画:Context → Container → Component(→ Code,很少用)。
- 例子
- Context:系统、用户、外部服务。Container:web、API、数据库、queue。
- 代价
- 图要和代码在同一个 PR 里更新。
- 何时使用
- 当需要向新来的人或另一个团队解释这个系统时。
- 关联
- 展示 Bounded Contexts 和边界;sequence diagram 负责调用的顺序。
架构师要和工程师、产品、业务、安全和法务打交道。每个听众听的是不同的语言。
Risk Translation — 把风险翻译成听众的语言
医生对病人不会只说拉丁文的名字。他会解释哪里会疼、疗程多长、要避免什么。
这就是 risk translation 的实践。你可以对工程师说:"支付 callback 上没有 idempotency。"业务需要听到的是:"如果 webhook 重试,我们可能重复扣款,或者把商品发两次;这是财务和声誉上的风险。"
Stakeholder mapping 帮你看清谁在乎什么:产品要 time-to-market,安全要降低风险,财务要可预测的成本,客服要可理解的行为,工程要可维护性。
- 问题
- 技术风险听起来像行话,业务根本听不进去。
- 思路
- 把风险翻译成对这个具体听众有意义的后果。
- 例子
"callback 上没有 idempotency" → "我们可能重复扣款——一个财务和声誉上的风险。"
- 代价
- 你得知道每个 stakeholder 在乎什么,并说他们的语言。
- 何时使用
- 每当一个决策取决于工程之外的人。
- 关联
- 例子是 Idempotency;业务那一侧是 NFR 和 cost of delay。
RFC — 书面提案能给争论降温
白板前的争论,往往是嗓门最大的人赢。书面提案则迫使论点脱离嗓门,独立站得住。
RFC(Request for Comments)描述问题、约束、各种选项、选定的方案、风险和 rollout 计划。好的 RFC 会在决策变成规定之前,邀请大家来批评。
Disagree and commit 只有在一场诚实的讨论之后才成立。如果团队没被听见,那不是 commit——而是压制。
- 问题
- 白板前的争论,赢的是嗓门最大的,而不是最有道理的。
- 思路
- 以书面形式:问题、约束、选项、决策、风险、rollout。批评发生在决策之前。
- 例子
- 换用新的 queue 之前:一份 RFC、一周的评论,然后是决策和一份 ADR。
- 代价
- 比"开个会就定了"更慢;需要一个负责人把它推到决策。
- 何时使用
- 用于涉及多个团队、或撤销代价很高的决策。
- 关联
- 结果记入 ADR;是 Influence without Authority 的一件工具。
架构师往往仍是个人贡献者。他的权威来自清晰的思考、信任,以及让团队变得更强的能力。
Influence without Authority
指挥不会演奏每一件乐器,但没有指挥,乐团很快就会变成一群各弹各曲的才子。
这就是 influence without authority 的概念。架构师不该成为每个决策的瓶颈。他的工作是定原则、讲清边界、培养人,并在犯错代价很高的地方出手。
架构师的 mentoring 不是"照我做的做"。而是教会提问:你见过哪些备选方案、放弃了什么、打算怎么验证风险、责任到哪里为止。
- 问题
- 架构师亲自拍板所有事,成了每个团队都要排的队。
- 思路
- 通过原则、边界和问题来施加影响,而不是下命令。
- 例子
- 不说"就这么做",而是问:"你看到了哪些备选方案,打算怎么验证风险?"
- 代价
- 当下更慢;需要花几年才能建立起来的信任。
- 何时使用
- 当你没有直属下属、但决策仍然要做的时候。
- 关联
- 通过 RFC 和 ADR 起作用;反面是 Ivory Tower Architect。
Delegation with Guardrails — 委派不等于转移责任
船长可以把舵交出去,但不能说船的航向不再是他的事。
一部分架构决策应该由各团队来做。否则架构师就成了一条队。但必须有 guardrails(护栏):原则、review、ADR、平台约束、quality gate。
当一个决策不可逆、破坏系统的 invariant、带来安全风险,或影响多个团队时,架构师出手。
- 问题
- 要么所有事都经过架构师(排队),要么没人对航向负责。
- 思路
- 团队在护栏之内自己决定;架构师对航向负责,并在明确的信号出现时出手。
- 例子
- 团队自己挑日志库;改 tenancy 模型则只能和架构师一起做。
- 代价
- 护栏要被表述清楚、写下来,并尽可能自动检查。
- 何时使用
- 当有不止一个团队、却只有一个架构师时。
- 关联
"不可逆"这个信号来自 Reversible vs Irreversible;护栏就是 ADR 和 quality gate。
架构只有放在业务目标的 context 里才有意义。否则它就是昂贵的工程审美。
Non-functional Requirements — 同样是业务需求
对一家商店来说,"黑色星期五结账能用"不是技术细节——而是钱。
Non-functional requirements(NFR)——性能、可用性、安全、合规、可运维性——不是"技术上的想要"。它们是产品的属性。架构师把它们翻译成业务影响:损失的收入、监管风险、客服成本、上市的速度。
Cost of delay 帮你讨论的不只是做这件事的成本,还有等待的成本。有时需要一笔没有即时 ROI 的架构投入,因为没有它,接下来的功能会开始变得更贵。
- 问题
- 系统质量被当成"技术上的想要",一再被推后。
- 思路
- NFR 是产品属性;用收入、风险和成本来讨论它们。
- 例子
- "结账能撑过黑色星期五"是钱,不是细节。
- 代价
- 你得用数字估算影响,哪怕只是粗略的。
- 何时使用
- 当架构工作和功能争抢时间的时候。
- 关联
用数字表达:SLO;被推后的工作:Technical Debt;沟通的语言:Risk Translation。
Build vs Buy — 不是面子问题
不是每家公司都该为了开灯,自己造一座发电站。
**自建(Build)**给你控制权和差异化。**购买(Buy)**给你速度,并降低运维负担。Vendor lock-in 是真实的风险,但它本身并不构成反对购买的论据。问题是:如果供应商改价格、改 API 或降质量,退出要花多少?
架构师帮助产品看清 capability map:哪些能力是业务的核心,哪些更适合买。
- 问题
- 有人出于面子自己造,有人什么都买、却没有退出计划。
- 思路
- 自建能让业务差异化的部分;购买通用的部分。对所有买来的东西,估算退出成本。
- 例子
- 你自己的 domain 引擎——自建;邮件发送或支付——通常购买。
- 代价
- 自建:时间,以及永久的维护。购买:依赖供应商的价格、API 和质量。
- 何时使用
- 每次说"我们自己写一个"之前。
- 关联
- 陷阱是 NIH 综合症;Dependency Inversion 有助于隔离供应商。
当代码由 agent 来写,架构师的价值转移到规格说明、架构思考和质量把控上。
Specification-driven Development — spec 的质量成了瓶颈
如果施工队快了十倍,起约束作用的就不再是砌砖的速度。而是图纸的质量。
AI agent 加快了实现,但并没有消除架构。一个提得糟糕的任务,只是更快地变成糟糕的代码。Specification-driven development 的意思是:首要的产物是对行为、边界、invariant、测试和验收标准的规格说明。
在 AI-native 的流程里,架构师写更少的底层代码,写更多精确的文字。他审的是 diff、测试、架构边界,以及与 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 的代码因为能编译、看起来很自信,就被接受了。
- 思路
- 和对人一样的自动化 quality gate,一样的 review。
- 例子
- Scenario eval 能发现 agent 只处理了 happy path,忘了重复支付的情形。
- 代价
- 自动化检查和 import 边界要提前搭好。
- 何时使用
- agent 的每一个 PR,没有例外。
- 关联
检查与 Specification-driven Development 的一致性;边界来自 Dependency Inversion。
架构师恰恰因为听起来有说服力,才可能很危险。了解这些陷阱能省下好几年。
Ivory Tower Architect
有人在山顶上画了一座完美的城市,却从不走下山去看看人们实际往哪儿走。
这就是 **ivory tower architect(象牙塔架构师)**反模式:决策是在远离代码、运维和团队的地方做出的。图很漂亮,实现却遭殃。解药很简单:读 PR、看 incident、和工程师聊天、亲自走一遍改动的路径。
- 症状
- 团队实现不了的漂亮图纸。
- 为什么有害
- 决策无视真实的代码、运维和人。
- 解药
- 读 PR、看 incident、和工程师聊天、亲自走一遍改动的路径。
- 关联
Influence without Authority 的反面;形式压过现实,也属于 Cargo Cult。
Resume-driven Development 与 Cargo Cult
如果你既没有水也没有消防员,却因为能给邻居留下印象而买一辆消防车,那就怪了。
Resume-driven development 为了简历上多一行而选技术。Cargo cult engineering 照搬别人决策的形式,却没有它背后的理由。NIH 综合症(Not Invented Here)逼你在成熟的外部工具本可以做得更好的地方,自己再写一套。
另一个陷阱是 technical perfectionism(技术完美主义)。那个从未到达用户手里的完美架构,会输给一个足够好、能交付、还能演进的系统。
- 症状
- 决策的理由是时髦、别人的例子或美感——而不是某个问题。
- 为什么有害
- 你付出了复杂技术的代价,却没得到它的好处,或者压根没能到达用户。
- 例子
- 三个人的团队上 Kubernetes 和 microservices,"因为 Netflix 就这么干"。
- 解药
- 从问题出发,诚实地说出每个选项的代价。
- 关联
靠 Trade-offs 和 Build vs Buy 来治;常常催生 Distributed Monolith。
架构是一场无限游戏。技术会变,但提出正确问题的能力只会越来越强。
T-shaped / M-shaped Skills — 从系统中学习,而不是从 hype 里学
厨师不是靠看刀具广告成长的,而是靠理解食材、火候、时间和味道。
新技术值得用一连串问题去学:它解决什么问题、引入什么代价、已经在哪里失败过、有哪些备选方案、要满足什么条件它才会变得有用。
T-shaped skills 让你在一个领域有深度,在相邻领域有广度。M-shaped skills 出现在成熟的架构师身上:几根深的支柱——数据、分布式、安全、产品、AI 工具。
- 问题
- 人们学遍每一项时髦的技术,却没获得深度和判断力。
- 思路
- 通过"问题与代价"的提问去学技术;培养几根深的支柱。
- 例子
一个新数据库:它解决什么问题、已经在哪里失败过、要满足什么条件它才对我们有帮助。
- 代价
- 比读发布公告慢;深度要花好几年。
- 何时使用
- 雷达上每出现一项新技术的时候。
- 关联
- 防范 Resume-driven Development 的手段;这些支柱就是本课程的各个主题。
Learning in Public — 加速成熟
当你把一个决策写出来讲清楚时,你思维里的漏洞会在生产环境发现它们之前就暴露出来。
Learning in public 的意思是公开地、用文字来学习。写 ADR、RFC、工程笔记、post-mortem,以及对决策的简短复盘。向比你强的架构师学习——不只是他们选了什么,还有他们是怎么推理的。
人们会去模仿的那类架构师,通常不是懂最多术语的那个。而是在他身边,团队开始问出更好问题的那个人。
- 问题
- 生产环境会比你的同事更早发现你推理里的缺陷。
- 思路
- 把决策和复盘写下来,分享出去,从反馈中学习。
- 例子
- ADR、RFC、工程笔记、post-mortem、对决策的简短复盘。
- 代价
- 写作的时间,以及展示未完成想法的勇气。
- 何时使用
- 一直——架构师就是这样成长的。
- 关联
用到 ADR、RFC 和 post-mortem;和 Influence without Authority 一样,让团队更强。
- 我能不带自我防御地说出一个决策的 trade-off。
- 我明白系统里真相的来源在哪里。
- 我分得清可逆决策和不可逆决策。
- 我能用不同的话,向工程、产品和业务解释这套架构。
- 我写决策的方式,让它一年后仍然能被看懂。
- 我不会把复杂度藏在"best practice"这个词后面。
课程地图:一切如何相连
旅程终点的一张地铁线路图:现在你看到了线路在哪里交汇。
思维塑造结构、数据和质量。结构被记录进文档,质量由安全和运维来验证,文档和风险说服业务。这一切合在一起,扩展成 AI-native 的工作方式和个人成长。
速查表:本课程的全部 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 之前就看到它们的工程师。