速成课 · No. 02
不是一个头衔,不是一个职级,也不是一场考试的结果。而是一种看问题的眼光——今天的决策,塑造未来三年。
只讲精髓 · 细节自己补 · 用画面代替定义
架构师和工程师的区别在哪。不在于知识——而在于看问题的方式。
架构师往十年后看
工程师为周六要搬进来的那家人盖房子。架构师则问:"等他们有了第三个孩子呢?等母亲搬来同住呢?等他们想把一楼出租呢?"
工程师解决被提出来的那个问题。架构师解决三年后它会变成的样子那个问题。这意味着:为还没用上的能力做设计,同时刻意舍弃那些听起来优雅、却永远用不上的能力。
架构师为后果负责,而不是为代码负责
木匠把钉子钉歪了,一天就能修好。架构师把承重墙放错了位置,房子修不了——只能将就着住。
工程师的错误,一周就能修好。架构师的错误,公司要为它付好几年的账——付在技术债里、付在流失的团队里、付在错过的机会里。所以架构师必须在_第一行代码写下之前_就想清楚,并且有勇气在架构扛不住真实需求时叫停工程。
架构师质疑任务,而不只是质疑方案
病人进门就要一张处方。好医生先问哪里疼——也许会发现,他点名要的那种药本来就开错了。
当有人说"做 X"时,工程师问"我该怎么做 X"。架构师问的是**"我们为什么需要 X,有没有别的东西能解决真正的问题"**。任务常常本身就提错了——而某人点单要来的那个方案,一年后变成了问题本身。
工程师把东西造对。架构师造对的东西。
架构是"选择放弃什么"的艺术。不存在完美的方案。
每个决策都赢得点什么,也失去点什么
自行车比走路快,但下雨会淋湿。汽车不淋雨,但堵在路上动不了。地铁不堵,但不会送你到家门口。
工程师选一项技术时,常常在找"最好的那个"。那是一种错觉。架构师明白:任何选择都是沿着几条轴的折中——开发速度 vs 可维护性、简单 vs 灵活、现在的成本 vs 三年后的成本。把这些轴说出来、并有意识地选,这才是架构的活儿。
简单的方案,几乎总比复杂的方案更对
瑞士军刀什么都能干,但什么都干得不好。一把好刀,切起来胜过瑞士军刀里所有那些小兄弟。
工程师怕漏掉一个需求,于是把系统塞满"将来可能用得上"的一切。架构师明白:每一个没人用的能力,都是对开发的永久征税。复杂度要在它说得通的时候有意识地引入;默认情况下——选那个简单、好改的方案。
难以撤销的决策,值得更多关注
餐厅里的菜可以退。婚礼上的那声"我愿意",退不了。
不是所有架构决策"做错的代价"都一样。选一门编程语言或一个数据库,几乎不可逆。选一个日志库,一天就能撤销。架构师在不可逆的决策上花掉不成比例的时间,在可逆的决策上则跑得很快。
技术债是一种有意识的选择,不是懒惰
如果你知道何时、如何偿还,一笔商业贷款没问题。一笔没有还款计划的贷款,是通往破产的路。
有时_当下_对的做法,就是先发一个_以后_必须重写的东西。这很正常。**不正常的是不承认它是一笔债。**架构师会记下:接下了哪些折中、为什么,以及什么时候需要回头重新审视。
如何把一个大问题拆成一些组件,使每个都能被单独地理解、改动和替换。
拆分是架构师的核心技能
你没法整条吞下一头鲸。但如果切成一口大小的块,就可以。
一个大系统是无法被理解的。好的架构是一组小零件,每个都能被孤立地看懂。如果解释一个组件需要把另外三个拉进来——拆分就是坏的。
内部高内聚,外部低耦合
一套好公寓:厨房围绕一个目的来布置(灶台挨着水槽),而它只和邻居共用一面墙。
模块内部,一切都应该关于同一件事。模块之间——接触最少,通过一个清晰的 interface。如果你在一个模块里改点东西,就不得不去碰另外五个——它们之间的耦合就太高了。
分层讲的是依赖的方向
房子的楼层:三楼立在二楼上,二楼立在一楼上,一楼立在地基上。你没法把地基放到三楼去。
在好的架构里,业务逻辑不知道数据库、web UI 或 framework 的存在。内层不依赖外层。当你需要换数据库或换 framework 时,业务逻辑原封不动。这就是一句话版的 Clean Architecture。
interface 比 implementation 更重要
你不在乎楼里跑的是哪个牌子的电梯。你在乎的是按钮用法一致、门可靠地开合、而且安静。
当一个模块和另一个对话时,重要的是它们之间的 contract,而不是对方内部怎么造的。先设计边界(API、class interface、message schema),再设计实现。实现可以自由替换;contract 只在充分意识到代价时才改。
单体还是微服务——这是成熟度问题,不是时髦问题
小厨房里一个厨师出一道菜,比十个厨师在 Slack 上扯皮要快。一小时一百道菜,十个厨师更行。
微服务解决的是组织规模和负载的问题,不是代码质量的问题。一个小团队上微服务,是没人要的复杂度。架构师从单体起步,等真正的痛出现时再拆,而不是"因为它流行"。
系统里最耐久的部分。代码 pattern 会被重写;数据模型却能活很多年。
数据模型比其他一切都活得久
房子的地基,熬过了一个世纪里在它之上盖了又拆的所有墙和屋顶。
语言会走,framework 会换,UI 会被重画十遍。但开头定下来的数据结构会一直活着——因为在运行中的系统上改它,代价极大。它是架构师最先花时间的东西,也是最不急着去动的东西。
single source of truth——每个事实只住在一个地方
如果家里每个人都各记一张购物清单,两个人会买牛奶,而没人买面包。
同一份信息存在两处,是通往分歧的必然之路。架构师决定系统里每个事实住在哪里。如果数据被复制了(比如为了性能),就必须明确:什么是 source of truth,什么只是 cache 或 projection。
状态就是复杂度;越少越好
干净的桌面让工作更轻松。堆满杂物的桌面逼你记住东西放在哪,还不停地分散你的注意力。
每一份存在某处的状态,都是可能失同步、可能丢失、可能和别的状态相矛盾的东西。好的架构追求无记忆的函数和短命的变量。状态只留在它真正需要的地方——通常是数据库。
transaction 讲的是意图的原子性
一笔转账是两个动作,却是一个意图。要么两笔写入都发生,要么都不发生。只发生一半就是灾难。
当几处改动必须作为一个单元发生时,你需要 transaction。架构师清楚系统里哪些操作是原子的、哪些可能部分失败、以及如何与之共处。在分布式系统里,这成了一个核心问题(two-phase commit、saga、幂等性)。
cache 是第二份真相,而它会撒谎
冰箱上的日历比掏手机查更快。但要是有人在手机上改了日程——冰箱已经在撒谎了。
cache 给你速度,却拿走了新鲜度的保证。架构师始终明白:什么被缓存了、缓存多久、以及 cache 撒谎时会发生什么。最难的 cache 问题不是怎么加一个——而是_怎么让它失效_。
最大的问题不诞生在组件内部,而诞生在组件之间。
系统边界是一份 contract,不是代码
国与国之间的边界不是一道栅栏——而是规则:什么能带过去、谁能进来、需要哪本护照。
一个 API、一个 message schema、一种文件格式——它们是对所有使用者的一份承诺。改 contract 就是改承诺,而那代价很大。好的架构师把 contract 设计成可以扩展而不破坏现有客户端(加字段,永不删字段)。
同步还是异步——这是耦合的选择
打电话:双方必须同时在线。写信:收件人方便时再读。
一次同步调用在时间上把两个 service 绑在一起:如果接收方宕了,发送方也卡住。message queue 打破这种耦合:发送方扔下一封信就走人。它给你韧性,但加上了复杂度——延迟、重复、ordering。
幂等性——一个操作可以被安全地重复
电梯被叫到三楼。就算十个人又按一遍同一个按钮,电梯照样去三楼,什么都不会坏。
在真实系统里,调用会被重试——因为 timeout、网络故障、重启。**一个重试起来很危险的操作,就是一颗定时炸弹。**架构师默认就把关键操作(支付、消息处理)设计成幂等的。
外部依赖永远是风险
如果晚饭取决于街对面的披萨店——没什么大不了,有备选。如果取决于全城唯一的肉类供应商——那就要命了。
每一个外部系统(API、service、库)都可能宕机、变慢,或改掉它的 contract。架构师决定那发生时会怎样:是整个系统垮掉,还是带着降级继续运行?常常值得把外部依赖隔离在你自己的一层后面——这样以后能替换掉。
不是"系统做什么",而是"它做得多好"。那些客户永远不会明说、却一定会要求的东西。
安全不是一个 feature,而是系统的一种生活方式
博物馆里,安保不是门口的一个摄像头——而是层层设防:闸机、保安、每个房间的摄像头、展柜上的锁。
你没法在最后才把安全栓上去。它要被建进每一层:认证、授权、加密、隔离、审计、最小权限。架构师从第一天就想着它,并在每个决策处都问:"如果攻击者从这里进来呢?"
三个人的出租车不该开得像高铁。高铁也开不进小巷子。
"让它快点"是个毫无意义的需求。架构师把它表述成:每秒多少请求、能容忍多少延迟、峰值时系统怎么表现、超过极限会怎样。过早优化是浪费;完全不想则是灾难。
可扩展性是不重写就能生长的能力
一栋设计时就留了"加盖二楼"选项的房子,不必拆掉就能扩建。没留这个选项的房子——只能推倒重来。
系统必须朝两个方向生长:更多用户(横向——加服务器)和更多功能(模块化——加模块而不破坏旧的)。这是被烤进设计里的,不是事后栓上去的。
可靠性是系统在出故障时的行为
好船不是不会沉的那种(那种不存在)——而是有十个隔舱的那种:一个破了,其余的照样浮着。
系统的每个部分终将损坏。架构师不去试图阻止它——而是设计系统如何在故障中存活。故障隔离、retry、graceful degradation、fallback。核心规则:一个组件的故障,绝不能拖垮整个系统。
observability——看不见的东西修不了
一辆没有油表、没有温度计、没有油压指示的车。它一路开下去,直到彻底趴窝。
系统必须讲出它自己的状态:logs、metrics、traces。当凌晨三点出岔子时,你只有几分钟去定位问题。如果 observability 不是一开始就设计进去的,事后就来不及加了——你连该往哪看都不知道。
系统会活很久。它会被改动。好的架构,就是那种容易被改动的架构。
为变化做好准备,比今天就做对更重要
在一座新城里,路修得比今天所需的更宽。十年后车多了,而窄路本来是要被刨掉重修的。
架构师不去试图预测每一个未来的 feature(那不可能)。他留出"不破坏现有东西就能加 feature"的余地。这意味着:清晰的边界、可扩展的 contract、把"做什么"和"怎么做"分开。
从旧房子搬到新房子,是个长达数月的过程。你没法把一切都扔掉、再全部重买。
当一个系统需要改变(新数据库、新协议、新数据模型)——那不是"推倒重写",而是"带着系统穿过一次过渡"。真实系统在改动期间仍在运行。架构师从一开始就想着迁移路径,否则过渡会变得不可能。
删除比添加更重要
你没法把柜子无限扩大。有时候你得扔掉旧的,才能给新的腾出地方。
任何成功的系统都会积累死代码、没人用的 feature、过时的集成。**那是每个开发者每天都在交的税。**架构师定期问:"我们能删掉什么?"——而有时,那是一年里最有用的架构决策。
最好的架构,是那个本来就不需要的架构
最坚固的墙,是那道永远不必被拆掉的墙,因为它背后没塞进任何多余的东西。
有时对的架构决策是不做一个架构决策。别"为了将来"引入一个抽象层。别"因为总有一天"去拆单体。别"以防万一"造一个插件系统。好的架构师知道何时该说"还不到时候"或"我们不需要这个"。
没人理解的架构,等于不存在。这份活儿有一半是把它传达出去。
架构决策要写下来,否则就丢了
口头遗嘱随作者一同消亡。书面遗嘱熬过一代又一代。
每一个重要的架构决策都需要被记下:选了什么、备选是什么、为什么被否决、接受了哪些折中。这份文档现在不需要——它是一年后才需要的,那时新人加入,问"这为什么做得这么奇怪?"。标准格式是 ADR(Architecture Decision Record)。
一张图胜过千言万语
用文字描述怎么走到一栋房子,又长又常常没用。给一张地图,一秒就懂。
架构师手里总有一张系统图:有哪些组件、它们怎么连、边界在哪。不必完美——能用就行。它是用来快速给新人解释、并让你看见系统全貌的。C4 model(Context、Container、Component、Code)简单,对大多数情况都够用。
架构师要讲到非工程师也能听懂
好医生用病人听得懂的话解释诊断。差医生堆术语,病人点点头,什么也没懂。
如果架构师只能把一个决策解释给其他工程师听,那他漏掉了一半的活儿。业务 stakeholder、产品、法务、安全——他们都需要理解后果。把一个技术决策翻译成业务语言(成本、风险、时间),是架构师的核心技能之一。
对每个请求都点头的外科医生会失去病人。好医生会在手术会致命时拒绝。
团队常常提出一些听起来合理、却制造长期问题的决策。架构师必须能顶回去、解释风险,有时还得守住底线——哪怕不受欢迎。同时——也要能承认自己错了,并在对方的反驳更有力时改掉决策。
架构师在做任何决策前、以及一天结束时,会过一遍的清单。
- 真正在解决的是什么问题? 不是被提出来的那个任务,而是它背后的问题。 - 有哪些备选? 至少三个, 包括"什么都不做"。 - 这个选择我失去了什么? 至少说出两个坏处。 - 这个决策有多可逆? 可逆——就快点 定。不可逆——就多想想。 - 两三年后它会是什么样? 不是理想状态——是现实状态。 - 除了我,还有谁需要 理解这个决策? 以及我会怎么向他们解释。
- 这个系统里最脆弱的是什么? 负载下什么最先垮? - 哪些改动容易,哪些痛苦? 那是架构质量的尺度。 - 每个重要事实的真相,住在哪里? 它的副本又在哪——cache、replica、projection。 - 组件之间的边界,哪些 是显式的,哪些是偶然的? 偶然的那些是技术债。 - 每个外部依赖坏掉时会怎样? 一次一个。 - 如果今天 从头开始,我会怎么做得不一样? 那是演进的方向。
- 你花在讨论和设计上的时间,比写代码更多。 - 你常常在"怎么做"之前先问**"为什么"。 - 你大声说出 trade-off,而不是试图把它藏起来。 - 你写文档和图**,比大多数人都勤。 - 你看见整个系统,并能在 context 里解释它的任何一部分。 - 你想着一年后会接手这东西的人——并写得让他们用着舒服。 - 你偶尔会拒绝一项 漂亮的技术,因为它并不需要。
架构师不是知道得更多的人。是开始动手之前想得更久的人。