DDD 到底是银弹还是垃圾

crossoverJie

共 4225字,需浏览 9分钟

 ·

2021-11-01 18:30

每过一段时间,就会有人跳出来批判 DDD,这东西到底是垃圾还是银弹?

在某某公司干活的时候,有一批人声称要用 DDD 改造老旧系统,彻底解决核心流程规模化之后,项目难以维护的问题。之前某篇文章里的这张图,就是在用 DDD 做项目重构之前的烂摊子:

24d8298792422fc18a5280ddd6216499.webp

大家都很聪明,聪明到最后没人知道这新需求到底该往哪里写了。架构师们聚在一起学习 DDD 精神,产出学习报告,大半年过去,终于出了一些成果,有些子项目完成了用 DDD 进行的重构,年底可以拿来在酒会上邀功了,这下我们跟上了业界业务开发的主流方法论,可喜可贺,可喜可贺啊。

年末的时候部门内匿名提问的小纸条却向架构师们发直球:“为什么用了 DDD 以后,代码更难懂了?”,当时引得各位 DDD 推手尴尬无比,只能搪塞过去。

所以你觉得我是要批判么?那倒不是。

在某某司工作期间,到离职前,我把市面上所有 DDD 相关的书全部看了一遍。对其理论体系进行了完整的了解,可以说这套理论还是有些用处的,DDD 的理论诞生时间比较早,微服务的趋势是后来才爆发的。但微服务刚开始没有明确的拆分指导,人们发现 DDD 里的 bounded context 好像看着正好和服务的粒度是可以做个对应的,DDD 就成为了很多公司做业务的绝对主流方法论。

虽然很多技术人员不爱听,但是技术优劣和商业成败其实没什么必然的联系。同样的,方法论的对错和项目的成功与否也没有必然的关系。很多大公司做业务的人出来讲他们的技术方法论,这些人可能连自己的项目为啥成功都不一定知道,你指望能对你的场景产生直接帮助那可能是想多了。只是当听个乐,得个借鉴那可能还没什么问题。真的当金科玉律去执行,那撞一头包也正常。

DDD 和其它的工程方法论一样,没有办法证伪。放眼望去,纯粹堆砌人肉电池,不用 DDD 的项目也那么多成功的,大家的屁股还是在跟着公司的市值跑,哪家公司市值涨到中国第一了,那他们的技术就牛逼,这叫看市值决定价值观。如果一家公司靠 996 成功了,那 996 就是商业致胜的法宝,不学你就落后了。屁股可以决定脑袋嘛。

不过作为一个矜持的技术人员,我们在批判方法论的时候,还是应该要先对敌人有一些了解。

所以这一篇,我就简单带你们看看 DDD 里那些鬼名词都是什么意思。

战术设计与战略设计

整个 DDD 的方法论可以划分为两个大模块,战术设计、战略设计。这个你顾名思义,战术是小,战略是大。

  • 战术设计指的就是单模块级的设计,基本都是纯技术范畴的东西,只DDD 中给代码命名和模块设计给出了一些指导方法
  • 战略设计指的是大项目的模块拆分,这个和一线程序员关系不大,主要是公司内怎么在 bu 之间切蛋糕,bu 内怎么在 team 之间分赃

现在很多校招程序员可能或多或少都会碰到一些 OOP 方面的面试题,比如三大特性五大原则之类的,这些原则是设计项目的时候可以参考的原则, DDD 的战术设计就是在单模块上的各种命名规则和设计方法。只不过 OOP 这些原则的发明人(严格的说应该是汇总人)是 uncle bob,就是 《clean code》,《clean architecture》 的作者,这位白胡子爷爷大概率和 DDD 社区是尿不到一个壶里的,所以 《clean architecture》 这本书里只字未提 DDD。

公司的业务要怎么分派给不同的 bu(部门)去完成,这个一般是公司 CTO 或者 GM 要做的事情,部门内的项目要怎么分,哪些组做哪些事情。这是战略设计的范畴。DDD 声称战略设计也是要有方法的。这部分也是很多程序员认为最没用的一部分,我们后面来批判一下这些程序员。

战术设计

战术设计是纯技术范畴的东西,最让人头痛的就是里面的名词。

贫血模式和充血模式:DDD 推荐你用充血模式写代码,也就是按 OOP 的方式去做抽象,然后把行为挂在对象上,而不是以纯过程式 的方法去写代码。所谓的充血,就是对象本身有很多关联的行为,而不只是一个单纯的数据库的表的字段映射。DDD 声称的充血模式的优势是,大部分的行为被封装到了对象内部,这样我们在阅读流程代码的时候,是一目了然的,直接能看到 step 1,step 2,step 3。但实际即使我们不用 OOP 来组织行为,一样可以把不同的业务 step 做好封装和复用。有些公司的服务粒度拆的特别细,比如只有 5000-10000 行代码,在 DDD 里声称的充血模式的优势没有那么明显。

值对象和实体:这个也挺离谱的,值对象就是纯粹的数值、文本类型,比如:

type person struct {
  age int
  name string
}

就是值对象,如果我们给这个 person 加一个 id,让它能表示 person 的唯一性了;

type person struct {
  id  int
  age int
  name string
}

那它就是实体了。

这两个概念只是给我们日常用的对象们进行了一个简单的分类,没什么大用处。

聚合根:DDD 里所谓的聚合根是事务粒度的 entity,也就是说,如果我们对 db 进行存取,那么我们就需要有一个聚合根,如果在一个事务里需要操作多张表,那么就需要给多张表关联一个单独的聚合根。

d937cd7ef98d71c4333b82fed002016a.webp

聚合根可以由一个 entity 组成,也可以由多个 entity 组成,就是你完成一个 db 事务的时候有多少关联的对象 ,那可能就有多少在同一个聚合根下面的 entity。

六边形架构:这个所谓的六边形架构,就是除了业务以外的所有外部变化都抽象成 adapter interface 做适配。如果你稍微理解一点点点依赖反转,那应该知道怎么样去做这种抽象。如果你一点都不了解,那我建议你去看看 go-micro 的代码。如果看不懂,建议还是尽早转行吧~

7eaaaa869d8f85f56a9f5d8109674931.webp

六边形架构这东西主要是名字实在起的太奇怪,在 《clean architecture》那本书里,uncle bob 也给过一张图:

4a1aacf34da190215227f63cfb71cd2a.webp

《evolutionary architecture》这本书出自造词大本营 thoughtworks 的员工之手,里面有一个 plugin architecture,就是有些人特别喜欢说的插件化架构:

2eb978c618b4aade71a0051d94fa02dd.webp

Repo Pattern:DDD 理论认为我们业务项目的存储这一层是可能经常变化的,所以就专门存储层的 interface 设计单独拿出来,称为 Repo Pattern,这东西实在没啥可说的,find,getlist,save,你只要有一点点 orm 经验,里面有啥接口应该自己都可以默写出来。

事实是在 2021 年,我们的存储系统基本是不太可能做切换的了,即使切换,那些新兴的社区存储系统也会支持 MySQL 协议,基础设施想要侵入代码,那简单是大逆不道啊。

领域事件:其实就是做上下游解耦的 kafka message,我们用 domain event 显得会更洋气一些。

领域服务:Domain service,顾名思义,你认为是自己部门或者组内的局部 api gateway 也是可以的。

综上,如果你是在大公司一线工作了两三年的程序员,上面这些东西应该马上就能理解,没有啥值得说的。如果是为了去架构师大会上秀一秀,你总得包装一下让自己显得没那么土吧?

战略设计

Domain:领域,你们公司是干啥的,你都不知道吗?

Core Domain:你们公司的卖货的,那卖货就是你们与其它竞争对手的关键竞争环节。这就是核心域,就是核心业务,为啥聪明人都往核心业务挤?核心业务的汤也比边缘业务的饭好啊。

SubDomain:你们公司的卖货的,但是用户没法付钱,那也没法干,支付就是子领域。

Supporting Domain:你们公司是卖货的,但是客户想看一些指标,你总得有系统能支持吧?可能就是些写写 SQL 的系统。支持域。

Generic Domain:你管你们公司干什么呢?员工的在职离职,工资发放总得有系统能支持吧,这些就是通用域。

除了第一个 Domain ,其余四个 domain 重要性逐级递减,递减的意思是,如果公司要裁员,那是从下面往上面裁。

前面我说有些程序员觉得 DDD 战略设计没用,你连自己所在的组,从事的工作职责对于公司来说重不重要都不清楚,那被裁的时候也别哭哦。

统一语言:这个就更好理解了,比如跳水这个词,你说跳水的时候指的是这个:

d3e6860c2b3cdf1b4b7b3064ebe1444a.webp

而你同事说跳水的时候指的是这个:

15ebc94c91ac382c6eeeec5aba5ce96c.webp

这里你们聊的是工作,那说明你们一定不是在同一个上下文里工作,可能你们俩一个在体育赛事部门,另一个可能是在金融部门, DDD 认为可以用统一语言来进行领域划分工作。划分后在同一个上下文内,同一个名词大家说出来意思一致。这就是 Bounded Context,  ain。

既然拆分了,如果我们还在同一个 domain 内,那完成业务流程是需要协作的,这个不同 Context 的协作方式就叫 Context Maps 或者 Integration Type。

名词很恶心,但具体的方法就两种,两个微服务要么通过 RPC 通信,要么通过 MQ 通信。

如果通过 RPC 通信,那 callee 一般是 caller 的爹,很多时候 callee 挂了是要影响 caller 的(当然也有熔断之类的方法避免一起死)。

通过通过 MQ 通信,那上游一般是下游的爹,因为上游一个重构,下游们可能就都炸了,最终一致都是屁话,多少公司的最终一致都是靠人肉修的。

这种爹和儿子的关系就是 Conformist。如果爹能多考虑一下儿子的需求,那就是 Customer-Supplier 关系,毕竟顾客名义上还是上帝。如果跨系统有一些需要共享的定义,比如公司里的业务分类,可能大家都要从某个系统的 PHP 文件里解析出来在自己的系统里去用,那这时候可能得去使用别人的代码,这种叫 Shared-Kernel,Kernel 一改,大家一起死。

最后,有时候我们可以用一个叫 ACL 的东西拦住上游的一些修改对我们的业务逻辑侵入:

防腐层:Anti-Corruption-Layer,就是我要把外部系统的变化拦截在对接层,不要让别人的屎甩到我身上。

讲到这里,基本的概念我们已经都过一遍了,你要说 DDD 一点用处都没有,那我也是不同意的,至少看完了这些书,我知道去哪里能赚到更多的钱了。

额外再说一句,DDD 的书写的都不怎么样。

浏览 25
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报
评论
图片
表情
推荐
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报