2.2 高内聚低耦合:切得深也要切得准


2.2 高内聚低耦合:切得深也要切得准

摘要:服务边界要同时满足"高内聚"和"低耦合",前者决定一个服务管的是不是"一件事",后者决定它跟别人是不是只说必要的几句话。本文把这两个词从口号拆成可衡量的东西,教你识别共享表、隐式依赖这类不自然耦合,并给出"先聚后切"的实操顺序。

上节我们用限界上下文按语义砍刀,方向对了。可刀口一深,就该谈具体手感:切出来的每一块,内部是不是咬得够紧(内聚),对外是不是只有必要的咧嘴(耦合)? 这节把它讲成可操作的东西。

内聚:一个服务该"一个人唱一台戏"

内聚指一个模块内部的职责"拧成一股绳"。高内聚的服务意味着:属于它的数据、规则、行为都在它体内,改它的理由也只有一个方向。举个例子,一段"下单"逻辑如果有下面的样子,内聚已经出问题:

class CheckoutService: def place_order(self, order): self.order_db.save(order) # 订单数据 self.payment_client.charge(order) # 埋了一颗支付 self.stock_client.decrement(order) # 又调了库存 self.sms.snapshot(order.user, "已下单") # 还发短信

这一段把下单、付款、扣库存、通知全塞进来了。拆服务时,这四个动作大概率归属四个不同上下文(交易、支付、仓配、用户)。它们的"高内聚"在于各自管好自己,而不是在 checkout 里全都内联。判断标准很简单:这段改动常见的触发原因,是不是只有一个方向? 下单加个校验要动它,扣库存规则要动它,短信模板也要动它,那它就是不内聚。

耦合:对外只暴露"该说的那几句"

耦合讲的是服务之间"联系的强度"。我们追求的是低耦合:契约清晰、依赖单一、能被替换。

把耦合分两档看:

  • 自然的耦合:通过明确的 API 调用、发布事件。A 要 B 的什么,走 B 的公开接口。这类耦合健康,因为它是显式的、可审计的。
  • 不自然的耦合:共享数据库表、共享缓存里的隐式字段、靠"我知道你后端长什么样"来同步规则。这类是拆服务后最容易死灰复燃的雷——因为它把依赖藏了起来,互相改字段的两个人甚至不知道在互相踩脚。

两个耦合征兆值得到处排查

征兆一:共享表。两个服务写同一张表,等于约定"字段语义我只信 ddl"。一旦一方加了字段、改了长度,另一方要么崩要么悄悄错。正确做法是"数据库每服务",这个概念我们在第 4 章展开,这里先记住它是对抗共享表耦合的正解。

征兆二:隐式事件耦合。服务 B 靠"扫描服务 A 的数据变化"来得知业务状态——比如轮询 A 的表看看有没有待处理的单。这种轮询式同步既慢又脆,正确的解是 A 主动发布领域事件,B 订阅。这又通向了我们第 3 章的消息队列。

实操顺序:先聚后切

给一个乱糟糟的边界做手术,务必"先聚后切",别反过来:

  1. 先聚:把本属于同一个上下文的散落逻辑先收拢到一块(哪怕还都在目录里)。
  2. 再切:收拢好了之后,沿边界一次性切开,把 API 面焊牢。
  3. 后验证:找三个人从不同角色读代码,确认"这个服务该不该管这块"大家答案一致。

一个反例是急着"先切后聚":还没理清哪个上下文就开砍,结果把同一个业务逻辑半挂在两个服务里,比切开前更糟。宁可先花一周聚拢,也不要第二天就拆出十个服务来。

陷阱:用"文件多不多"判断内聚

有些团队喜欢用"这个服务几个文件、几行代码"来间接判断内聚,把"服务要小"等同于"服务要高内聚"。大方向偏了。真正的标准是职责唯一与变更方向单一,而不是体量。一个 500 行但只干一件"库存扣减"的服务,内聚度很高;一个 80 行却同时管订单校验、物流跟踪、风控的服务,内聚度很低。别用行数自我安慰。

用"变更影响面"复查一轮边界

前面讲了直觉上的内聚与耦合,最后再给一道能落地的复查尺:当你改了这项能力的一行逻辑,它会影响几个部署单元?

假设你改"下单时校验优惠券是否过期"。理想下,这个改动只碰交易服务,别的服务无感——这是高内聚、低耦合。但如果你发现同一行改动要先改订单服务的表、再去库存服务打个补丁、还要通知支付服务的超时规则,那这一"业务"其实被摊在了多个部署单元里——内聚和耦合同时不合格。

这道复查不用等上线,在画边界时就能推演:把未来半年最常见的十次改动逐条写下来,看每一条会牵连几个服务。牵连数越少、越稳定收敛在 1,边界越健康;如果十条里有五六条都横跨多服务,请毫不犹豫回来重画刀线。这个"变更冲击面=1"的测试,比任何文件行数的指标都更能暴露你到底切得准不准。

三类最常见的"伪独立",一眼识破

理论说完,落回最容易踩的三个"看着像独立、其实没独立"的形态,能省你不少返工:

  1. "包接口但共享表"的伪独立:代码分成两个服务、接口也暴露了,可底层还在写同一张表——这就是把耦合从代码挪到了数据层,换汤不换药。
  2. "各忙各的但概念一致"的伪独立:两个服务里各自维护一套"其实描述同一件事"的数据结构和规则,却没有任何同步——看起来低耦合,一碰到真实业务就对不上账。
  3. "全做在网关/主流程里"的伪独立:服务是拆出来了,可真正关键的逻辑全都下沉在一个共享的入口或主流程里,拆分只是换了层皮,核心还是那一个点。

认这三类的共同点:真正的独立是"数据独立 + 规则独立 + 生命周期独立"三者一起成立,只满足其中一个,都不能叫高内聚。下次验收"拆没拆干净"时,就用这三条逐项过,比纠结服务数要准得多。

本节要点

  • 高内聚=职责唯一、变更方向单一,不是体量小
  • 低耦合=主要通过公开 API / 事件协作,依赖可审计被换
  • 共享表和轮询式依赖是两种最典型的"不自然耦合"
  • "数据库每服务"是对抗共享表的正解,事件驱动对抗隐式耦合
  • 手术顺序:先聚拢再切开,别急着用行数当内聚的替身

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