2.2 限界上下文:语义的主权边界


2.2 限界上下文:语义的主权边界

本节摘要:限界上下文(Bounded Context)是一条显式声明的边界,边界之内,同一个模型、同一套词汇保持一致;边界之外,不承诺任何一致性。它回答 2.1 留下的问题——"词的定义域"到底怎么划。本节先破除"全公司一个统一大模型"的执念,再给出划定边界的三条依据,最后澄清三个最常见的身份误解:限界上下文不是模块、不是微服务、更不是一张数据库表。

为什么不该只有一个大模型

2.1 的事故报告里有一条被划了重点的整改建议:"建议公司统一订单模型,消灭同名异义。"这个建议听起来无比正确,几乎所有团队的第一反应都是它。但它恰恰是战略设计要推翻的第一个直觉。

推演一下"统一大模型"会发生什么。青柚商城试着把交易、仓储、财务对"订单"的共识合并成一个 Order 类:它要有交易流水号、买家、金额(交易视角),要有拣货单号、波次、承运方(仓储视角),要有应收科目、发票抬头(财务视角)。合并刚做完,麻烦接踵而至:营销活动改退款规则,要动这个大类的退款字段;仓储换承运商,也要动它——每一个域的变更都变成了全公司的变更。更糟的是语义污染:财务同事在评审时问"订单为什么有波次字段",半年后交易代码里开始有人依赖波次字段做判断,两个域的逻辑缠死了。

统一模型的失败根因可以用一句话说破:一致性是有物理半径的。让四个人对一句话的理解保持一致,靠吼一声就行;让四十个人保持一致,需要流程;让四个系统、几十万行代码保持一致,需要的不是流程而是运气。与其追求一个守不住的大一致,不如承认一致只在局部成立,把局部圈出来,把圈外的差异显式化。这个"圈",就是限界上下文。

把定义说完整:限界上下文是一条边界,边界之内,模型与语言严格一致——每个词有唯一含义,每条规则有唯一权威实现;边界之外,模型之间只通过显式契约往来。 它像语义上的主权国家:国境线内自立法度,国与国之间靠条约而非干涉。

边界划在哪:三条依据

主权边界不能拍脑袋画。实践中反复验证的三条依据,按优先级排列。

依据一:语言分歧点。 2.1 的词汇表把每个词的定义域标了出来——凡是同一个词在不同场景下判定时点或规则不同的地方,就是一条边界候选。青柚商城的第一版地图几乎完全从词汇表长出来:"订单"在交易与仓储之间分岔,切;"商品"在营销与库存之间分岔(营销叫"活动商品",关心价与量;库存叫"可售单元",关心仓与效期),切。语言分岔是业务本质复杂性的信号,跟着它画边界,边界就会长在业务的关节上而不是技术图纸上。

依据二:团队拓扑。 康威定律从组织侧给出同样的答案:系统的边界最好与团队的沟通结构重合。 交易组与仓储组本来就是两个团队,各自有排期、各自的发布节奏;如果它们的代码同处一个模块、共用一个库,每次协作都是跨团队联调。让"一个上下文一个团队长期所有",沟通成本就被关在了边界内。反过来的用法更常见也更危险:同一个团队维护五个"上下文",地图画得漂亮,人却不够分——这说明边界划细了,该合并。

依据三:数据所有权。 每份数据必须有唯一的所有者上下文,其他上下文只能引用它的 id 或订阅它的事实,不能直连它的表。这条依据最硬,因为它能被机器检查:翻一遍数据库直连与跨库联表清单,违反处就是边界被击穿的地方。青柚商城第一次自查就发现了九处跨系统联表,其中五处的联表字段还是另一张表的冗余副本。

青柚商城的第一张上下文地图

青柚商城的第一张上下文地图

三个身份误解,逐一拆掉

误解一:限界上下文就是代码模块。 模块是代码组织手段,上下文是语义边界。一个上下文最初往往以模块形态存在,但两者没有必然绑定:青柚商城在单体内用五个包模拟五个上下文,包之间的依赖规则按边界条约执行——这时候单体内部的包就是上下文的代码投影。反过来,一个微服务里塞两个上下文,服务再小边界也是错的。

误解二:限界上下文就是微服务。 微服务是部署单元,上下文是语义边界,前者是后者的候选承载方式而非定义。2.2 的地图画在先,拆服务在后——第 4 章会展开这个顺序为什么不能颠倒。现在只记一句:上下文是设计判断,服务是部署决策;先有前者,后者才有依据。

误解三:限界上下文就是数据库 schema。 schema 是所有权的物证之一,但边界首先活在模型与语言里。见过最贵的失败是把五个上下文的表拆进五个库,代码里却仍是同一个大模型——库拆了,语义没分家,跨库联表被 ORM 藏了起来,问题反而更难看见。

边界内的模型长什么样,用一对对照代码收尾。同一个词,两个上下文,两个模型:

// 交易上下文的订单:关心钱与承诺 export class TradeOrder { constructor( readonly id: OrderId, private items: OrderLine[], private payable: Money, // 交易语境的核心:应付金额 private status: TradeStatus, // 待支付 已支付 已关闭 ) {} markPaid(at: Instant): OrderPaid { /* 置位并产出事实 */ } } // 仓储上下文的订单:关心作业与实物 export class PickOrder { constructor( readonly id: PickOrderId, // 与 TradeOrder 的 id 没有继承关系 private lines: PickLine[], // 按库位排序的拣货行 private wave: WaveNo, // 归属波次 private state: PickState, // 待拣 拣货中 已交接 ) {} complete(pickResult: PickResult): Picked { /* 出库交接判定 */ } }

两个类没有任何字段级共享,只有一个 id 级的关联(交易订单 id 作为拣货单的业务引用)。重复的只是"订单"这个中文词,模型各归各家——这正是边界该有的样子。

带走的判断

  • 统一大模型的失败是结构性的:变更入口互相踩踏、语义互相污染,一致性半径撑不起一个公司。
  • 限界上下文的定义核心在"之内一致、之外契约"八个字,它是语义主权,不是技术划分。
  • 画边界按三条依据来:语言分歧点给位置,团队拓扑给粒度,数据所有权给可执行的检查。
  • 上下文不是模块、不是微服务、不是 schema——它是先于这三者的设计判断。

边界画完产生了新问题:上下文之间的往来走什么礼数?这是 2.4 上下文映射的事。但在那之前,先得回答一个更省钱的疑问——五个上下文是不是都值得用最贵的方式伺候?这是 2.3 子域划分的内容。


作者与出处
原作者: 灏天文库
来源:灏天文库
整理: 灏天文库整理
由灏天文库平台收录,内容或由平台用户上传,仅供学习交流
发布者: 作者: 灏天文库 转发
评论区 (0)
U