1、架构不是画图,是"最难改的决策"

写错一个函数,改它 10 分钟;选错一个架构,改它 3 个月。

架构(Architecture)是系统的骨架与规则:分哪些模块、谁依赖谁、数据怎么流、外部怎么接入。它的本质特征是——决定一旦定下,后续所有代码都要绕着它写,改起来的代价极高。

所以架构设计的目标不是"看起来高级",而是:

  • 满足当前的质量属性要求(性能、可维护、安全、可扩展)
  • 让未来的改动尽量便宜(可演进)
  • 让团队能并行工作(边界清晰)

一句话:架构是为"变化"留的余地。

2、先定质量属性:非功能需求驱动架构

架构不是凭空设计的,它由质量属性(Quality Attributes)驱动。同一个功能,在不同质量要求下,架构完全不同:

质量属性关键问题对架构的影响
性能响应要几毫秒?峰值 QPS?要不要缓存、要不要异步
可扩展性一年后用户翻 10 倍怎么办?能否水平扩展、有无状态
可维护性团队 3 人会变 10 人吗?模块边界、可测试性
可用性允许停多久?冗余、故障隔离、降级
安全数据敏感吗?谁能访问?鉴权位置、加密边界
成本预算多少?自建 vs 云服务、单体 vs 微服务

做法:为每个质量属性写一个可度量的场景:

当并发用户达到 500 时,任务列表接口响应时间 P95 ≤ 800ms。

问不出量化场景,说明这个属性根本没人真正在乎——不要为假想的"未来百万用户"过度设计。

3、模块划分:高内聚、低耦合

模块划分只有两条铁律:

  • 高内聚(High Cohesion):一个模块内部的东西"是同一件事"。
  • 低耦合(Low Coupling):模块之间尽量少知道对方的事。

怎么判断划分好不好? 问三个问题:

  1. 我改这个功能,要动几个模块?(动得越少越好,最好 1 个)
  2. 这个模块能不能单独测试?(依赖太多就难测)
  3. 新人要理解它,需要同时看懂几个模块?(越少越好)

一个实用的划分方法:按"变化的原因"切,而不是按"技术类型"切。

❌ 反例(按技术切):

controllers/   ← 所有业务的入口都在这
services/      ← 所有业务的逻辑都在这
models/        ← 所有数据都在这一坨

结果:改"任务"功能,要在三个目录里各改一处,且这三个目录里混着"用户""通知""统计"的各种代码。

✅ 正例(按业务能力切):

task/      (Task 的入口+逻辑+数据,自成一体)
user/
notify/

4、分层架构与依赖方向

单个模块内部,常用分层组织:

┌─────────────────────────────┐
│  接口层(API / Controller)  │  接收请求、参数校验、返回结果
├─────────────────────────────┤
│  应用层(Service / UseCase) │  编排流程、事务边界
├─────────────────────────────┤
│  领域层(Domain / Model)    │  核心业务规则(最稳定)
├─────────────────────────────┤
│  基础设施层(Repo / Client) │  数据库、外部服务、消息
└─────────────────────────────┘

最关键的一条规则:依赖只能指向内层(领域),不能反向。

  • ❌ 领域层里 import 数据库客户端 → 业务逻辑被技术绑死,测试必须连数据库。
  • ✅ 领域层只定义"我要存取什么"(接口),由基础设施层去实现(依赖倒置)。

这条规则带来的直接好处:核心业务规则可以脱离数据库、脱离 HTTP 单独测试——这正是第 20 课(测试)能高效开展的前提。

5、接口契约:模块之间怎么说话

模块边界要用契约(Contract)固定,而不是"直接调对方内部函数"。

契约要写清三件事:

  1. 输入:字段、类型、必填性、取值范围
  2. 输出:成功返回什么、失败返回什么(错误码/异常)
  3. 不变量:调用前后系统保证什么(幂等?有序?)

契约的三个层次:

层次形式谁关心
内部模块间函数签名 / Trait / Interface开发
服务间REST / gRPC / 消息 schema前后端、跨团队
外部系统开放 API 文档第三方
契约一旦发布,改它就是"破坏性变更"——要加版本、要公告、要过渡期。这正是第 31 课"兼容性与废弃策略"的伏笔。

6、常见架构风格与权衡

风格适合代价
分层单体中小项目、团队小、需求变化快规模大了会变"大泥球",需靠模块纪律维持
模块化单体想保留单体部署简单,又要清晰边界需要强制的模块访问规则(否则边界会烂)
六边形 / 整洁架构业务复杂、外部依赖多、要可测试初期代码量变多、抽象层多
事件驱动异步解耦、削峰、多消费者时序难追踪、最终一致难调试
微服务多团队独立交付、独立伸缩分布式复杂性、运维成本极高

一条铁律:不要用微服务解决"代码乱"的问题。 代码乱是模块划分问题,拆成微服务只会让它变成分布式的大泥球——而且更难修。

默认建议:中小团队优先模块化单体(Modular Monolith)——一个部署单元,内部边界清晰,等某个模块真的需要独立伸缩或独立交付时,再把它拆出去。这条路是可逆的。

7、演进式架构:敏捷里的架构怎么做

传统做法是"架构设计先行,一次定稿"。敏捷里更现实的做法是演进式架构(Evolutionary Architecture):

  • "最后一刻负责"(Last Responsible Moment):能推迟的决策就推迟,等信息足够再决定——比如"用哪个消息队列",在真要用之前不必定死。
  • 架构守护(Fitness Function):用自动化规则约束架构不腐化。例如 CI 里检查"领域层不得依赖基础设施层",违规即构建失败。
  • 每个迭代还一点架构债:重大结构改造拆成小步,随迭代推进,而不是攒到最后大重构。
  • 用 Spike 验证风险:拿不准的技术方案,花 1~2 天做技术探针(Spike),先写最小验证代码,用结果说话(第 11 课展开)。
敏捷不是不要架构,而是架构要与代码一起长,且每一步都可运行、可验证。

8、架构评审与产出物

架构要有能评审的产出物,而不是"某个大佬脑子里的想法"。

最小产出物清单:

  1. 模块边界图(谁依赖谁,用箭头画出来)
  2. 关键流程时序图(一个核心场景的调用链)
  3. 数据流图(数据从哪来、存哪、去哪)
  4. 质量属性场景表(可度量的非功能需求)
  5. ADR 清单(关键决策及其理由,第 11 课)

评审要问的五个问题:

  • 最大的技术风险是什么?有没有验证?
  • 哪个模块将来最可能变?变化时波及面多大?
  • 有没有循环依赖?
  • 出故障时,影响范围能限制住吗?
  • 新人多久能跑起来这个项目?

9、动手实验 🎯

任务:为「团队任务清单」设计一版架构。

  1. 写出 5 条质量属性场景(如"500 并发下 P95 ≤ 800ms"),并说明它如何影响设计。
  2. 画一张 模块边界图(ASCII 或纸笔均可),标出依赖方向,检查有无循环依赖。
  3. 为 1 个核心流程(创建任务并触发提醒)画时序图,标出契约(输入/输出/失败处理)。
  4. 选一个你真实的小项目,画出当前的架构,判断它属于哪种风格(分层单体/模块化单体/…)。
  5. 用"高内聚低耦合"三问做一次自检,找出 1 个最该拆开的模块,写出拆分方案。
  6. 写一条架构守护规则(例:领域层不得依赖任何数据库客户端),并说明怎么在 CI 里自动检查。

做完这步,你就有了"能评审、能落地、能守住"的架构骨架——下一课专门解决最难回头的一块:数据设计。

10、今天你带走的三句话

  1. 架构由质量属性驱动:写不出可度量的非功能需求,就不要开始谈架构。
  2. 高内聚低耦合 = 按"变化的原因"切模块,依赖方向只能指向核心业务,不能反向。
  3. 优先模块化单体,别用微服务治代码乱;架构要能演进,用 Spike 和架构守护来控制风险。

下一课进入数据设计:选型与建模——系统里最难改、也最该一次想清楚的部分。

标签: 软件工程, 项目管理, 开发流程

添加新评论