跳转至

说点题外话01 好耦和与坏耦和

你好,我是徐昊。今天我们专门来说点题外话(都知道,这门课里的题外话,都不是题外话)。

目前我们正好完成了旧约部分的学习。也就是说:

  • 通过第123讲,我们重新梳理了什么是领域驱动设计;
  • 在第456讲,了解了在落地领域驱动设计时,有哪些模式可以帮助我们更容易地实现模型;
  • 在第789讲,学习了不同的建模方法,以帮助我们展开业务维度;
  • 最后,在第1011讲,学习了怎么把模型映射为RESTful架构。

内容整体来说是比较紧凑的,知识密度也比较大,所以在进入新约部分的学习之前,我希望你能喘口气儿,稍微回顾一下我们学习过的内容。

要知道,反思是学习真正发生的时候。毕竟学习的目的是改变我们的行为和思维,而一味地摄入内容和信息呢,并不一定真的能达到你想要的效果

那么接下来,我将会集中回答一系列问题,帮助你更好地理解我们在旧约部分讲到的内容。因此,如果你有什么迫切的疑问,请尽快在评论区留言。时间有限,抓紧上车,入股不亏(当然,非官方版本是因为作者突发腰伤,需要休刊)。

今天,我想先来谈一谈耦合的问题。因为我从收到的留言和问题反馈中,发现很多同学对耦合已经有创伤后应激障碍(PTSD Post Traumatic Stress Disorder)了。有同学经常担心是不是会存在耦合,然后开始准备用各种手段消除耦合,等等。

但是我们需要知道,耦合从逻辑上讲,有必然耦合和偶然耦合两种。所谓必然耦合,就是如果你使用支付宝或者微信完成在线支付,那么你必须要跟支付宝或者微信存在耦合关系。这种耦合关系是完成业务的必须

对于这种耦合关系,你是无法消除的。也就是说,无论你怎么写代码,最终的结果都会转化为对支付宝或者微信的调用。当然,你也可以通过代码的组织,将这种耦和关系变得很隐蔽。比如通过事件驱动风格,神神秘秘地发一个消息,然后呢,后面会有个人帮你去完成调用。

然而消息发送在概念上和接口调用也没什么区别。所以当我们都处在概念层面时(比如我们现在在讨论必然耦合),其实你还是调用了对方的接口,所不同的是调用方法的差异。到底是直接调用,还是发个事件调用而已。

那么偶然耦合就不同了。什么样的情况算是偶然耦合呢?比如你现在要存一段数据,你可以选择把它存成文件,或者存在数据库里,再或者是区块链上链。不过,无论你选择哪种方式,其实都是偶然耦合,除非你有必须要这么存储的道理,否则你也只是随机地、偶然地选择了一种实现需要的方式而已。

所以对于耦合来说,我们本能地认为必然耦合是好的耦合,而偶然耦合是不好的。因为必然耦合中蕴含着必然性,也就是确定性;而偶然耦合里则包含着偶然性,也就是变化性。那么我们就需要处理掉偶然耦合。这恰恰是偶然耦合PTSD症状的源头之一。

然而让我们用偶然耦合和必然耦合再去看支付宝或者微信,你就会有另一种感受。我们是必然要使用支付宝或者微信吗?

显然不是!我们必然要使用的是网上支付服务,而我们只是偶然地选择了支付宝或者微信,作为我们实现网上支付的手段而已。

那么有什么办法可以让我们表达这种想法吗?有的。那就是提取业务能力。也就是说,我们不再必然耦合于支付宝和微信了,我们必然耦合于在线支付能力。然后呢,让支付宝和微信成为这种能力的实现。如下图所示:

从做法上来说,我们从业务上找到了业务真正所耦合的能力,然后将其他具体的供应商隐藏在能力之后。这样我们就完成了从偶然耦合到必然耦合的重构。事实上,这么做也深化了我们对业务的理解。而且也明确了我们所完成的功能,到底需要耦合于其他哪些业务能力。

注意,这个操作的关键在于,我们从业务上寻找到了对应的业务能力。从业务上寻找到的能力,是对业务本身理解的深化。而不是你随便编一个什么能力,只要能抽象过去就好。

但是一旦我们疏于理解业务,这个操作就演化成了著名的耦合PTSD:见不得对具体实现的依赖。于是我们会粗暴地将对接口依赖看做必然耦合,然后把对具体实现的依赖看成偶然耦合。所有具体实现都必须抽象一个接口,也不管它是不是能在业务上找到对应的能力。

这种盲目地对具体实现的恐惧,以及盲目地将所有具体实现都归结为偶然耦合的一刀切的做法,其实是我们行业的一大病,同时也是很多具有迷惑性的烂代码的来源。看起来整整齐齐,所有变化点都考虑到了,实则并没能简化问题,也没能降低理解难度,只是写的人自己爽了而已。

可以说,真正的明白人,只会消除不必要的偶然耦合,通过把对具体实现的偶然耦合,转化为对隐藏业务能力的必然耦合,从而简化对业务问题的理解

思考题

请从必然耦合和偶然耦合的角度思考一下,领域模型的多层架构,特别是基础设施层,到底应该是偶然耦合,还是必然耦合呢?

然后再回顾一下我们在4-6讲谈论的模式,其中哪些模式利用了将偶然耦合转化为必然耦合的技巧?我们又是如何识别偶然耦合的呢?

最后,如果你有什么迫切的疑问,或者想听的话题,请尽快在评论区留言。再说一遍,时间有限,抓紧上车。或许你的疑问,就会成为下一篇题外话的主题。

划重点,如果对课程内容有任何想法或建议,可以加专栏编辑的微信,和她交流,成为专栏的优质读者。微信号是:seekforli。

精选留言(14)
  • Jxin 👍(7) 💬(2)

    1.讨论松耦合的前提是想清楚,为什么我们要松耦合,或则说所谓的高耦合有什么坏处。 2.松耦合的目标是维持代码的稳定性,防止由于多种变化方向或者变法频率不一样的代码耦合在一起,导致牵一发动全身的窘境,使代码变得极不稳定,既存在风险又增加运维成本。 3.所以至少我们能得到一条线索。松耦合需要保证一个类或者一个函数下的代码,都是一个维度的东西。而一个维度的东西可以认为就是徐昊老师说的必然耦合的部分。 4.铺垫完,回到我们的问题。基础设施层,到底应该是偶然耦合,还是必然耦合呢?我的答案是这两块其实都有。 5.先说基础设施偶然耦合的部分,比如db存储。我们的目的是将数据持久化,对应到业务角度的模型就是所谓的仓储层接口,仓储层接口与聚合/领域服务是一个维度的东西,所以仓储层接口是必然耦合的部分。而仓储层接口的实现,这就属于偶然耦合的部分,选择什么存储方式,在业务模型的角度并不关心。同理的还有实现事件发布接口的mq框架。 6.接下来说基础设施必然耦合的部分,比如透传traceId/全局异常处理/打印中间件调用出入参等等。这些都是与业务无关,与系统技术维护相关,属于功能增强的部分。当然,这一部分也可以进一步抽象出必然部分和偶然部分,然后保证必然部分的文档和偶然部分的灵活替换。但是,大部分时候这些功能增强基本不变,且就算变了改动成本也很低,可以认为它们相对稳定了很多。那么再进一步区分它们的必然偶然就显得有点多此一举,毕竟它的风险和运维成本都很可控。所以我觉得这部分直接认为是必然部分也并无不可。(spring不是做了必然部分的抽象吗?我的答案是,基础框架和应用项目决策的优先级是不同的,它必须保证你的兼容性,哪怕这个兼容性永远不发挥效果) 7.综上所述,业务相关的耦合,因为维护成本大,需要区分必然与偶然部分,做抽象保证业务模型的稳定。业务无关的耦合,维护成本有限,做必然偶然部分的区分属于过度设计,我认为直接识别成必然部分不用抽象亦可。

    2021-07-20

  • xxx 👍(4) 💬(2)

    能展开讲讲为啥CQRS是邪教吗?Event Sourcing呢?

    2021-07-20

  • 小毛球 👍(3) 💬(1)

    必然耦合和偶然耦合不是绝对的,反而会相互转化,并且在不同时期,看问题的角度不同,必然和偶然也不同。所以做项目决策才会这么难

    2021-07-20

  • 下弦の月 👍(3) 💬(1)

    基础设施属于是完成业务的必须,从业务上有必然耦合性;至于选啥实现,则是偶然耦合。 前面有提到过,对于基础设施层,可以通过封装借口提供能力解耦合。 作为小白,希望能够更多的看到如本章所讲的基础知识内容。对新手来说,这些内容在段时间能给我带来的价值是非常巨大。我感觉最欠缺的,就是这些基础的。

    2021-07-20

  • 👍(2) 💬(1)

    基础设施层是偶然耦合,都是些具体实现。转换方法就是转换成实际的业务概念,这样从模型角度就屏蔽了细节,也就消除了偶然耦合。

    2021-07-20

  • 支离书 👍(1) 💬(1)

    “这种盲目地对具体实现的恐惧,以及盲目地将所有具体实现都归结为偶然耦合的一刀切的做法,其实是我们行业的一大病,同时也是很多具有迷惑性的烂代码的来源。看起来整整齐齐,所有变化点都考虑到了,实则并没能简化问题,也没能降低理解难度,只是写的人自己爽了而已。” 文中支付的例子,开始说是对支付宝微信“必然耦合”,后面又通过抽象出“在线支付能力”变成了对支付宝微信的“偶然耦合”。感觉这就是盲目的将所有具体实现都归结为偶然耦合的一刀切的做法了吧。毕竟在线支付发展到今天微信支付宝占市场90%以上,不可能不支持他们了。

    2021-08-17

  • 梦倚栏杆 👍(1) 💬(1)

    感觉这个抽象业务能力也是一个玄学,支付宝和微信支付可以抽象为在线支付,也可以抽象为支付,到底抽象到啥地步可能取决于一个人的长时间的工作积累。抽象的过粗和过细可能都不利于后续的维护

    2021-07-20

  • 码农戏码 👍(0) 💬(1)

    老师,能力供应商模式是不是拟人化的防腐层,平时使用样式也差不多,在domain定义接口,在基础层实现,根据DIP,通过spring Ioc实现,看着的确很一样 “不要用解决方案去定义问题。而是问题定义解决方案”能不能再详细说说

    2021-07-22

  • 樊野 👍(0) 💬(2)

    基础设施层实现的接口是必然耦合,这些接口在领域层中应该体现为业务能力而不是业务能力的具体实现。基础设施层本身是对接口(业务能力)的具体实现,是偶然耦合。

    2021-07-20

  • 码农戏码 👍(15) 💬(1)

    太精彩了,现在的程序员对耦合还真是PTSD,但来源不同,一种是自己的确受到了伤害,一种是听人说会受伤害,想到了著名的“猴子实验”。这篇文章绝对是对面向接口编程以及ISP认知的重要基础 面向接口编程,在OO世界里这是上帝,放之四海皆准,所以看每个项目所有的service都有一个interface,一个impl,相当整齐,问为什么要这么做呢?面向接口编程,防止未来扩展性,但现实项目从始至终都不会有第二种实现,但不能不这么做,这是公约,谁都得这样,不然就变成那只被打的猴子 Martin Fowler有提出role interface和header interface的定义,技术还是得反馈到业务的本质,从业务上去寻找业务能力,得到必须耦合的能力,给调用方提供稳定依赖

    2021-08-05

  • 邓志国 👍(2) 💬(0)

    基础设施层大多属于偶然耦合,可以消除;领域之间耦合属于必然耦合,没必要消除。听起来似乎也对。会不会导致领域对象之间循环依赖了呢?

    2021-07-21

  • wwlleo 👍(1) 💬(0)

    必然耦合是抽象的,只有抽象才稳定,比如支付这个抽象就是钱从A到B。 偶然耦合实际就是抽象的实现,随便选个实现就行了。支付宝,银行转账,微信,一手交钱一手交货都行。

    2021-08-01

  • JianXu 👍(0) 💬(0)

    我们公司对于Kubernetes 的使用是多cluster , 然后为了使用者方便又做了一个federation 层,比如k8s deployment , 我们就创建了一个 federated deployment 对象,简称FD ,FD对象里面可以声明在哪些cluster 里面部署deployment , 好了,现在我们要做auto scale 的能力。第一种声音说 社区HPA (horizontal auto scale) 的设计是把HPA 作为单独的对象去调用可以被auto scale 对象的scale 接口,所以我们也应该在federation 层做一个federated HPA ; 第二种声音是把auto scale 的能力做到FD 内部,也就是FD 内含auto scale spec 而不是由federated HPA 来驱动FD. 第二种声音的理由是FD 就是最终用户接触的operator . 而我无法判断哪一种才是合适的,老师在碰到这类问题的时候决策逻辑是如何的呢?

    2022-10-05

  • aoe 👍(0) 💬(0)

    看到微信、支付宝抽象成线上支付能力,想到了神探狄仁杰!当个程序员看不到问题的本质就摆脱不了加班的命运。

    2022-04-03