Software Architect · 模块 05

拆分不是把东西归进文件夹。它是在选择那条边界——在边界以内,一次改动始终是局部的。

Cohesion · coupling · bounded context · ownership

§ 01

好的边界,落在系统天然改变职责、改变语言、或改变变化速率的地方。

拆分从“变化的原因”开始

如果一扇门总因为仓库而被挪动,另一扇门总因为访客入口而被挪动,那就是楼里的两个区域。它们不该出现在同一面墙上。

一个 high cohesion(高内聚) 的组件,装的是那些"因同一个原因而变化"的东西。一个 low coupling(低耦合) 的组件,对邻居知之甚少,只通过清晰的 contract 对话。这两个属性,比任何 pattern 的名字都重要。

糟糕的拆分在 IDE 里往往看着挺整洁:controllersservicesrepositoriesutils。但如果一个小小的产品改动逼着你去碰五个层、三个领域,这个系统仍然切得很烂。好的拆分让你能改 checkout、billing 或 identity,而不必在脑子里把产品的其余部分全部加载进来。

边界同时也是 ownership

一张城市地图毫无用处,如果没人知道谁负责修桥、谁管红绿灯、出了事故该打给谁。

一条架构边界需要一个 owner。如果一个模块是"共享的",但人人都在改,它很快就会变成风险区。如果一个 bounded context 属于某个团队,那个团队就获得了演进内部模型的权利,以及保持外部 contract 稳定的义务。

所以拆分和 team topology 是绑在一起的。有时候,真正该问的不是"我们要抽出哪个 service",而是"哪个团队应该端到端地拥有这条 flow"。

§ 02

同一个领域,可以按表来切、按层来切,也可以按业务 flow 来切。通常第三种才管用。

例子:把 checkout 作为一个垂直切片

店里的收银台拥有整段购买旅程:购物车、价格、支付、收据。它不是"按钮的一层",也不是"SQL 的一层"。

在电商里,checkout 可以拥有 pricing snapshot、payment intent、order draft 和失败处理。Catalog 仍然拥有商品,identity 拥有用户,warehouse 拥有库存。checkout 不去复制别的团队的逻辑——它通过 contract 索要数据,并发布一个 OrderPlaced 事件。

这个垂直切片用起来很舒服:团队能完整看见用户 flow,而支付里的改动不会扩散到每一个子系统。

反例:把技术层当成唯一的边界

如果所有厨师都在一个房间切菜、在第二个房间煎、在第三个房间放盐,那么一份简单的订单就开始需要协调整栋楼。

系统被拆成了 user-serviceorder-servicepayment-service,但每个 service 内部坐着一个共享的 common-domain、一个共享数据库、一套共享的 validator。形式上有 service,功能上没有边界。

当团队按层划分时会更糟:一个前端团队、一个后端团队、一个数据库团队。每个 feature 都变成一个协调项目。这会拉高 lead time 和 cognitive load。

自检
  • 这个模块"变化的原因"是什么? - 团队能不能不跟五个邻居谈判就改动它? - 这条边界有 owner 吗? - 哪些数据和 invariant 真正属于这个 context?