需求定了,写第一行代码之前还有一个决定一切的问题:代码怎么组织。架构设计的对象是「模块的划分与模块间的关系」,它决定项目的天花板:好架构让后续每个功能「自然有地方放」,坏架构让三个月后的项目变成「每次改动都怕弄坏什么」的雷区。本章讲模块化的度量、经典架构模式、以及具体的设计决策方法。
模块化的一切原则都是这两个词的展开:
目标:高内聚、低耦合。为什么?因为变更的代价 = 改动的模块数 × 每处的理解成本。高内聚让「一个变更只碰一个模块」;低耦合让「碰这个模块不会波及别的」。数据结构课第 2 章「链表节点自引用」、Java 课第 7 章「封装」,都是这对原则在不同尺度的化身。
一个实用的内聚检查法:试着用一句话描述模块的职责,如果要说「和」,拆;如果说不清楚,拆(单一职责原则的通俗版)。
分层(layered)是使用率最高的架构:每层只调用下层,上层知道下层,反之不然:
表示层(UI/API 接口)
业务逻辑层(规则、流程)
数据访问层(数据库读写,数据库课)
数据库 / 外部服务
价值:依赖方向单一,测试和替换都简单(数据访问层换成 mock,业务层照样测)。Web 后端(MVC、三层架构)和这个 wiki 站(next 的 pages/components/lib 分目录)都是分层的变体。失效场景:层次太多导致「穿层透传」(一个字段改名的改动波及五层),层要少而清楚。
单体(monolith):所有功能一个进程一个部署单元。微服务(microservices):按业务域切成几十个独立服务,各自部署、各自的数据存储,服务间走网络调用(计网课第 10 章的 REST/RPC)。
| 单体 | 微服务 | |
|---|---|---|
| 开发/调试 | 简单(一个进程) | 复杂(分布式调试) |
| 部署 | 整体部署 | 独立部署,互不拖累 |
| 扩容 | 整体扩 | 热点服务单独扩 |
| 事务 | 本地事务(数据库课第 7 章) | 分布式事务,难 |
| 团队 | 少而全 | 多团队并行 |
结论先行:小团队、早期产品,单体是正确答案。微服务解决的是「组织规模」问题(几十个团队不能改同一个代码库),不是技术问题;在没人需要独立部署时引入微服务 = 白吃分布式的所有苦(网络失败、最终一致、运维复杂度)。先用好模块化的单体,等某个模块的伸缩需求真实出现再拆,「先单体后拆分」是业内(包括亚马逊早期)走过的路。
模块间的边界靠接口(第 5 章设计模式的舞台)。好接口的判断标准:
接口一旦有消费者就「冻结」了(改接口 = 协调所有消费者),所以接口设计的犯错成本远高于内部实现。经验法则:内部实现可以天天改,接口要当成对外的承诺来审慎设计。
架构决策(为什么用 PostgreSQL 不用 MongoDB、为什么单 体不上微服务)的推理过程,比决策本身更值钱(半年后有人来挑战时,当时的权衡是唯一辩护)。架构决策记录(ADR) 是轻量的解法:每个重大决策一份一页纸文档(背景 → 备选 → 决策 → 后果),放 git 里随代码演进。它治的是「祖传架构没人敢动、也没人知道为什么这么设计」的考古困境。