Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

导航书籍主页 | 完整目录 | 下一章:第2章


第1章 架构师的组合拳

复杂系统设计的方法论地图


1.1 软件架构的本质挑战

对一个做了 8 年左右后端或系统设计的工程师来说,软件架构真正难的地方,通常不是“有没有听过某个名词”,而是你会一遍又一遍遇到同一类问题:业务越来越复杂,团队越来越大,系统之间的调用越来越多,查询越来越重,规则越来越碎,而每一次新需求都在把这些问题继续放大。

很多人第一次认真接触架构,往往是从技术概念开始的:分层、DDD、CQRS、事件驱动、六边形架构、Clean Architecture。但真正在项目里工作几年之后,你会慢慢发现,架构设计并不是在概念之间“站队”,而是在不断回答几个非常现实的问题:

  • 业务边界到底应该怎么划,才能避免概念混乱?
  • 服务内部应该怎么组织,才能扛住持续变化?
  • 系统拆开之后,彼此之间怎么协作才不会越来越乱?
  • 代码怎么写、评审怎么做、上线怎么控,才能让设计不在落地时变形?

也正因为如此,本章并不打算把几个流行概念逐一解释一遍,而是想先做一件更重要的事:从工程实践的角度,沉淀软件架构面对的本质挑战,再引出架构师通常会如何打出一套“组合拳”来应对这些挑战。

1.1.1 为什么软件架构问题总会反复出现

软件架构之所以让人反复感到“似曾相识”,是因为很多系统最终都会落进相同的复杂性轨道。

项目刚开始时,大家通常只觉得“先把功能做出来”最重要。于是一个订单服务里,先有了下单接口,再加上支付回调、取消订单、超时关单、库存预占、营销校验、事件通知。前几个月看起来一切都还可控,但只要系统开始承接更多业务,问题就会陆续出现:

  • 原本只有几条状态流转,后来变成十几种特殊流程
  • 原本只有一个团队维护,后来变成多个团队协作
  • 原本只需要简单查询,后来页面需要展示越来越多聚合信息
  • 原本只改一处代码就够,后来一个需求要改接口、应用层、数据库、缓存和消息逻辑

这些问题反复出现,不是因为团队不努力,也不是因为某个框架选错了,而是因为复杂性会自然增长。如果没有一套稳定的边界、结构和协作方式,系统最终都会从“能跑”走向“难改”,再从“难改”走向“没人敢动”。

从这个角度看,架构设计并不是锦上添花,它更像是在系统复杂性不断上升时,为团队建立一套能够持续控住复杂性的秩序。

1.1.2 复杂系统的四类核心矛盾

如果把这些年在项目里反复遇到的问题抽象一下,大多数复杂系统最终都会面对四类矛盾。

第一类矛盾:业务复杂性不断上升

  • 商品、订单、库存、支付、营销之间的边界到底怎么划?
  • 同一个“订单”“商品”“价格”概念,在不同团队和不同系统里是否还是同一个意思?
  • 规则越来越多之后,它们到底应该落在流程里、模型里,还是散落在各个 Service 里?

第二类矛盾:系统必须持续应对变化

  • 业务规则会变,促销玩法会变,履约流程会变
  • 技术栈也会变,数据库、框架、消息中间件都可能调整
  • 如何让这些变化被限制在局部,而不是每次都牵动整条链路?

第三类矛盾:多人协作需要统一认知

  • 50+ 开发者同时开发,如何降低彼此干扰?
  • 产品、运营、业务专家和开发是否真的在使用同一套语言?
  • 新成员加入时,是否能快速理解系统边界和核心规则?

第四类矛盾:性能、一致性与可维护性彼此拉扯

  • 查询希望越快越好,往往需要宽表、缓存、搜索和反范式化
  • 写入希望越稳越好,往往需要事务、一致性和严格规则保护
  • 如果为了性能不断破坏模型,系统会变脏;如果为了模型纯净牺牲响应能力,业务体验又会变差

这些矛盾单独看都不新鲜,但真正困难的地方在于:它们几乎总是一起出现。这也是为什么软件架构很少有“一招鲜”,而更像是一套围绕不同问题分层出招的组合动作。

1.1.3 电商系统为什么是观察架构问题的最佳样本

电商系统之所以适合拿来讨论架构,不是因为它更“高级”,而是因为它几乎天然把前面四类矛盾全部放大了。

从业务上看,电商系统同时涉及商品、库存、计价、营销、订单、支付、履约、售后,业务规则彼此交叉,非常容易出现边界模糊和语义冲突。

从流量上看,商品详情、搜索、列表页有很高的读压力;下单、支付、库存扣减等链路又对一致性极其敏感。这意味着系统既要面对高并发,又不能把正确性让位给性能。

从组织上看,电商项目通常不是一个小团队就能长期覆盖的。商品团队、营销团队、交易团队、支付团队、履约团队会围绕同一个平台长期协作。边界一旦划不清,协作成本会非常快地吞掉研发效率。

也正因为如此,电商系统非常适合拿来回答这样一个问题:面对真实复杂系统,架构师到底需要哪些方法论,它们分别在什么阶段解决什么问题?


1.2 架构师如何应对这些挑战

如果说上一节回答的是“问题到底是什么”,那么这一节要回答的是另一个更重要的问题:架构师通常会按什么顺序来应对这些问题?

在真实项目中,成熟的架构设计很少是“先选一个流行概念,再把所有问题往里塞”。更常见的做法是按问题的层次逐步处理:先确定边界,再稳定内部结构,再设计系统之间的协作方式,最后用代码规范和质量机制把这套设计守住。

这也是本书第一部分的主线。

1.2.1 先做业务边界

复杂系统最先要解决的,通常不是“选哪种分层”,而是“系统到底应该怎么切”。

如果业务边界没有先划清楚,那么后面几乎所有设计都会失焦:

  • 团队不知道哪个规则属于哪个系统
  • 同一个概念在不同服务里被重复定义
  • 上下游关系长期靠口头约定维持
  • 一个变更会反复穿透多个模块和多个团队

因此,架构师首先要做的,往往是识别核心域、支撑域、通用域,划分限界上下文,建立通用语言,并明确上下文之间的关系模式。换句话说,先画地图,再开始谈地图里的建筑。

这一步对应本书的 第 2 章《业务边界与战略设计》

1.2.2 再建系统内部秩序

边界画清楚之后,问题会自然转向单个系统内部:这个服务应该如何分层?依赖方向怎么控制?业务规则落在哪?读写路径是否应该继续共用同一套模型?

这一步其实是在解决“单个系统内部如何建立秩序”的问题。也是大多数团队最熟悉、但最容易混在一起的一步。

在工程实践里,这一层通常不是靠单一方法论完成的,而是几种方法一起配合:

  • 用三层架构先建立基本职责分层
  • 用 Clean Architecture 约束依赖方向
  • 用 DDD 战术设计承载复杂业务规则
  • 在读写矛盾明显的地方引入 CQRS

这一步对应本书的 第 3 章《系统内部结构设计》

1.2.3 再处理系统之间的协作

系统一旦拆分成多个上下文、多个服务,新的问题就出现了:调用如何解耦?跨服务流程怎么编排?消息如何可靠投递?事务边界跨不过去时,一致性又怎么保证?

也就是说,边界和内部结构解决的是“单个系统怎么设计”,而系统间协作解决的是“多个系统如何一起工作”。

这一层关注的不再只是类、包和目录,而是:

  • 事件驱动与同步调用如何搭配
  • 防腐层和集成契约如何守住边界
  • Outbox 如何处理双写问题
  • Saga 和补偿事务如何处理长流程一致性

这一步对应本书的 第 4 章《系统集成与一致性设计》

1.2.4 最后落实代码与质量保障

很多设计图看起来都很漂亮,但真正决定系统能否长期维持下去的,往往不是图画得多完整,而是代码怎么写、评审怎么做、上线怎么控。

如果没有编码原则,系统内部结构很快会在日常开发中被打穿;如果没有质量保障机制,再好的边界和架构也会在交付压力下被不断开洞。

因此,架构的最后一公里,通常落在两个层面:

  • 代码层面:如何写出可读、可改、可测试的实现
  • 质量层面:如何通过评审、检查清单和上线前验证守住设计

这两部分分别对应本书的:

  • 第 5 章《编码原则与设计模式》
  • 第 6 章《架构质量保障》

如果把这一整套顺序收成一句话,就是:

先划清业务边界,再建立系统内部秩序,再处理系统之间的协作,最后通过代码原则与质量机制把架构真正落地。


1.3 架构设计的方法论地图

理解了问题层次和应对顺序之后,我们就可以把常见的方法论重新放回同一张地图中。

在实际落地中,架构师通常不会只选一种方法论,而是把它们嵌套使用,形成一个完整闭环。这也是“组合拳”这个说法最核心的含义:不是每种方法论都解决所有问题,而是每一种方法论都在处理复杂系统中的一个关键维度。

1.3.1 DDD:解决业务边界与领域建模

DDD 在这套组合拳中有两层作用。

第一层是战略设计:帮助架构师识别核心域、划分限界上下文、建立通用语言和上下文映射。它回答的是“系统应该怎么切”的问题。

第二层是战术设计:帮助团队在单个上下文内部用聚合根、实体、值对象、领域事件表达复杂业务规则。它回答的是“切出来的系统内部,规则应该怎么承载”的问题。

因此,DDD 既属于“先划边界”,也会延伸到“内部结构设计”。这也是为什么本书把它拆成了第 2 章和第 3 章两部分来讲。

1.3.2 三层架构:建立工程分层的默认起点

三层架构不是最“先进”的方法论,但它仍然是很多项目最实用的起点。

它先解决的是一个非常现实的问题:代码应该按什么职责落位,团队才能顺利协作。表现层、业务逻辑层、数据访问层的分工,让系统至少先具备了基本秩序。

从架构师视角看,三层架构的价值不在于它能一次解决所有问题,而在于它能以很低的成本让项目快速从“无结构”进入“有分层”的状态。因此,它经常成为后续引入更强结构的起点。

1.3.3 Clean Architecture:约束依赖方向

如果说三层架构先回答“代码放在哪”,那么 Clean Architecture 进一步回答“核心业务应该依赖谁”。

它最核心的要求是依赖方向向内:外层技术可以依赖内层业务,但内层业务不应该知道数据库、框架和中间件的具体存在。这个约束看起来很抽象,但它直接影响系统是否可测试、可替换、可长期演进。

很多项目在目录上看起来仍然是三层,但一旦在依赖方向上开始服从这个规则,它其实就已经在朝 Clean Architecture 靠拢了。

1.3.4 CQRS:解决读写目标冲突

CQRS 解决的是另一个常见矛盾:同一套模型能否同时服务好读和写。

在简单系统里,共用统一模型通常更经济;但在复杂系统里,写侧关注一致性和规则保护,读侧关注查询性能和展示效率,二者的目标很容易发生冲突。这个时候,继续强行共用一条路径,往往会让双方互相拖累。

CQRS 的价值,正在于允许我们承认这种冲突,并把命令侧和查询侧分别优化。它经常出现在系统内部结构演进的后期,同时又会自然和事件驱动、投影、最终一致性衔接起来。

1.3.5 事件驱动与一致性设计:解决系统间协作

当系统之间开始协作时,单个服务内部的分层已经不够了。新的问题变成了:事件怎么发、消息怎么投、双写怎么解、长流程怎么补偿、一致性怎么守。

这一组方法论通常包括:

  • 事件驱动
  • 集成模式
  • Outbox Pattern
  • Saga / 补偿事务
  • 最终一致性设计

它们不再主要解决“单个系统内部怎么组织”,而是解决“多个系统之间如何可靠协作”。这也是为什么它们会放到第 4 章集中讨论。

1.3.6 编码原则与质量保障:守住架构落地质量

一套架构设计如果不能稳定落到代码和交付流程里,最终就会退化成 PPT 上的结构。

因此,架构师的组合拳里还缺最后两类能力:

  • 用编码原则和设计模式保证实现层不把架构意图写坏
  • 用评审、检查清单、测试与上线前验证保证架构不在交付时变形

这两类能力看起来不像 DDD 或 CQRS 那样“有名词感”,但它们恰恰决定了一套架构能不能在团队里真正活下来。

如果把整套方法论再压缩成一句话,就是:

DDD 帮助我们认清业务边界,三层架构和 Clean Architecture 帮助我们建立系统内部秩序,CQRS 帮助我们在读写冲突中做结构优化,事件驱动与一致性设计帮助我们处理系统间协作,而编码原则与质量保障负责把这一切真正守住。


1.4 本章小结

1.4.1 核心观点回顾

本章想传达的核心观点其实很简单:

  • 软件架构面对的不是单一问题,而是一组会长期反复出现的复杂性挑战
  • 架构师真正做的,不是“选择某一个流行概念”,而是按问题层次打一套组合拳
  • 这套组合拳通常遵循固定顺序:先做业务边界,再建系统内部秩序,再处理系统间协作,最后通过代码原则和质量机制把设计守住

也就是说,架构设计的本质不是追逐术语,而是控制复杂性如何在系统中传播。

1.4.2 与后续章节的关系

这一章只是全书的总序,负责建立方法论地图。后续章节会沿着这条主线逐步展开:

  • 第 2 章《业务边界与战略设计》:先讨论系统应该如何划清边界
  • 第 3 章《系统内部结构设计》:讨论单个系统内部如何建立秩序
  • 第 4 章《系统集成与一致性设计》:讨论多个系统之间如何协作
  • 第 5 章《编码原则与设计模式》:讨论架构如何真正落到代码
  • 第 6 章《架构质量保障》:讨论如何用评审和验证机制守住架构质量

读完这一章之后,你不必已经掌握所有细节,但应该先建立一个判断框架:当系统变复杂时,架构师会从哪些层次出手,又该如何把这些方法论组合起来。

1.4.3 延伸阅读

  • Robert C. Martin, Clean Architecture: A Craftsman’s Guide to Software Structure and Design, 2017
  • Eric Evans, Domain-Driven Design: Tackling Complexity in the Heart of Software, 2003
  • Vaughn Vernon, Implementing Domain-Driven Design, 2013
  • Martin Fowler, CQRS Pattern

下一章第2章 业务边界与战略设计 将深入讲解限界上下文、通用语言与上下文映射,回答系统应该如何划分边界、团队如何围绕边界协作。


导航返回目录 | 书籍主页 | 下一章:第2章