相对于DDD圈子各种各样错误百出的玄学文章,这篇《DDD诊所》已经算是想要认真探讨问题的难得之作,说得粗俗一点——可以算是翔中的豆芽菜或金针菇了。但这里作者要去当医生治疗病人了(虽然不收诊金),那就得好好说一说了。其实我要批评的几点,在之前我写的《DDD领域驱动设计批评文集》序列文章中都提过(因此本文我会偷懒,放上大量我写过的文章的内容截图),这篇文章也不幸一一命中,这并非偶然。从我开始写《DDD领域驱动设计批评-合集》序列文章,到现在已经有4年了,在这期间:*没有DDD圈子人士当面(包括线下和线上)对我恶语相向,都是很礼貌地叫我“潘老师”;*【我推测】大多数DDD圈子人士没有稍为认真地扫一遍我写的文章;*少数DDD圈子人士表现出很有诚意和我讨论和向我学习建模。我说,如果乐意学习或和我讨论,可以先看书和做题,我出了几百道扫码自测题,做对大半后,如果有问题可以再讨论——理由参见:*但是(往往真正要说的藏在这里)!目前为止,我还没有发现DDD圈子有人按照我的建议认真去做——这个我看得出来的。我描述的上述若干现象,看起来有点诡异甚至互相矛盾,对吧?可以看我写的《小甜甜和牛夫人?》,理解了这个圈子的创新观和风气,就不觉得诡异了。(可能有的读者会说,你自我感觉怎么这么好?大家都很忙,你阿猫阿狗随便写点什么,人家凭什么要认真看,认真学习或认真反驳?问题在于,至少在这一点点上,我不是阿猫阿狗啊。毕竟圈子吹捧的这个东西,我还是最早引进的人之一,参见《小甜甜和牛夫人?》)下面,我按照《DDD诊所》的文章结构依次评点,其中最严厉的批评出现在【治疗建议】部分。《DDD诊所》的这部分罗列了系统的一些功能以及存在的问题。2022年,《DDD诊所》作者所在公司Thoughtworks的另一位同事曾来上我的课,并提供了一个“企业持续交付流水线平台”的案例项目在课上剖析,现在又看到《DDD诊所》这篇文章里也提到类似项目,不知道两者之间有没有关系。以下几张图就是当时我在课上和学员讨论后针对“企业持续交付流水线平台”案例画的第一个迭代的现状业务序列图、改进后业务序列图和系统用例图。
能认认真真画一张类图,光是这一点就已经压倒很多DDD文章。很多DDD文章列出两三个类,甚至有的就一个类,然后就开始往上批量刷**Service、**Repository。《DDD诊所》所给出的类图中,类的个数远远超越了那些文章,而且我仔细看了类图,表示法上也基本没有什么问题。类图右侧的“触发规则”、“阶段质量门禁项”的比较规则,这些“规则”不是独立的,它们隐藏在类以及关系之中,应该在类图上显式表达。很多人经常犯的错误就是,轻飘飘地用一个类“**规则”搞定,似乎这样做之后“**规则”就会从天上掉下来。这其实是逃避了问题,因为封装“**规则”相关的知识,很可能就是你的系统存在的理由。(如果“**规则”里面的逻辑已经由第三方搞定,那么必定还会有另外一个“**规则”的逻辑是你的系统存在的理由,钱哪里是那么好挣的?)如果一个类的名称是以下面这些结尾:er、or、器、策略、Strategy、Policy、规则、Rule、算法、Algorithm……一定要警惕,有可能这种类是逃避了真正问题的废话。
上面提到的“企业持续交付流水线平台”,我当时在课上画过一个类图,也可以作为参考。灰色部分表达了规则。
规则表达清楚了,剩下的就相对简单了,无非是在规则上玩游戏,把结果记录下来。不过,我们看到的很多建模者,能力仅能达到“把结果记录下来”,很难做到“显式描述规则”。而“显式描述规则”的缺失,往往会带来另一个错误:事物规格和事物不分。例如,图4中,“阶段”、“步骤”中的“参数”指的是什么?是名称还是值?项目的哪一个阶段(步骤)应该有什么样的参数?这是一个知识。某个具体项目的某个阶段(步骤)什么时间发生的,具体参数值是多少?这是另一个知识。
图中这样一写“下一个步骤是阶段的步骤列表中的下一个”就完了?然后好事就从天上掉下来了?步骤的顺序在哪里呢?这可不是在“步骤”中加个“序号”属性或者在“步骤”上建一个自反关联就能解决的。一个阶段有哪些步骤?它们之间的顺序如何?这是一个知识。张三的项目当前到了某某阶段的某某步骤,这是另一个知识。这两个知识不分开,必定会导致大量冗余的数据(即对象的属性值)。这一部分描述了使用聚合/组合时要警惕的问题。这个部分的问题主要是造词,“***综合症”之类。(1)《DDD诊所》引用的文字存在严重的翻译错误。为了“治疗”,《DDD诊所》先摘了一段《领域驱动设计》中译本的文字:
可以看出,《DDD诊所》的作者所摘的文字应该来自人民邮电出版社2016年的译本。
图10中的重点是:不变式是类的不变式,是类所封装的逻辑,而不是说有个孤立的“不变式”,然后几个类去满足它。而这一点,《DDD诊所》的作者应该不了解,例如,他在文章里说“订单与订单行之间存在固定规则”:
有趣的是,《DDD诊所》的作者还引了维基百科作为佐证:
《DDD诊所》里用的方法应该是(或者自以为是)面向对象方法吧?如果不是面向对象,而是作者刚创新的“领域驱动设计大模型人工智能数智化敏捷方法”,那以下文字就当我没说。不变式所表达的本来就是一段规则,你爱怎么组织都可以。面向对象的组织,就是把这些规则按类组织,从而把逻辑封装在类中。员工年龄不得超过性别的年龄上限,例如男性45,女性50。
可以看到,不变式是放在类上面的,并不是像图8、图16和图18一样,放在类关系上。我们还可以观察到,“员工”的直接或间接属性有:年龄、员工性别.年龄上限、员工性别.名称。要是这些属性的值的组合没有任何约束(例如年龄46,性别男),那么系统的复杂度会大大降低——可惜,现实中不是这样(这正是图10中Bjarne Stroustrup所强调的)。“员工”的不变式正是这些属性之间的约束的一个表达式——并不是像图8那样意淫式地随便写句话;同样,“部门”的直接和间接属性,除了“员工”集合之外,还包括上面列出的“员工”的直接或间接属性,而“部门”的不变式正是这些属性之间的约束的一个表达式——并不是像图8那样意淫式地随便写句话。那可不可以把“员工”和“部门”的关联方向倒过来?可以的。这时,原来加在“员工”上的不变式应该不用修改,但原来加在“部门”上的不变式就要挪到员工上(因为“部门”访问不到刚才那些属性了),而且表达式也要修改——肯定能表达,无非是集合运算,区别在于更直接还是更弯弯绕而已。从这一点也可以看出,到底是“员工”知道“部门”合适,还是“部门”知道“员工”更合适?不变式可以作为参考依据之一。要提醒的是,不变式仅仅是表达逻辑的选择之一,而且也仅能表达部分逻辑。
DDD圈子为什么独独吹捧这个呢?哦,因为《领域驱动设计》提到了它,更关键的是,这个圈子是一个封闭的互吹互捧的圈子。
(2)问题更大的是,《DDD诊所》的作者对译文的糟糕没有敏感性。最简单的一点,Invariant译为“固定规则”的,【我见过的】是独此一家!列举几本有一定水平的书(说的是原著),有译作“不变式”的:
图14 Meilir Page-Jones《UML面向对象设计基础》(人民邮电出版社)截图
图15 Ian Graham《面向对象方法原理与实践(原书第3版)》(机械工业出版社)截图以上所列,都不是UMLChina译,以免被认为故意挑选。《DDD诊所》的作者居然不觉得有违和感,是因为只看过《领域驱动设计》的这个译本,而没有看过其他书?如果《DDD诊所》的作者手上资源有限,例如,没有别的译本,没有英文原文,没看过其他书……那应该想办法去丰富自己的资源,等学习好了再给人看病。(3)问题最大的,《DDD诊所》的作者缺乏对“废话”的基本敏感。不了解或误解(1)中我说的不变式的知识,这个好说,花时间学习就是。
《DDD诊所》的作者把“A更新的时候,会不会引起B的某个属性的更新”,“如果两个实体暂时不一致,是否会导致难以承受的业务后果”、“订单的总价等于订单行的总价之和”看作不变式要表达的内容。像图中“订单的总价等于订单行的总价之和”,如果这个公式被严格遵守,那么“订单”类的“总价”属性在逻辑上就是冗余的,应该删掉。既然“订单”的“总价”属性没了,图16上所标的不变式也就不存在了。那怎样才是“订单”需要表达的不变式呢,我举下面的例子供参考:
任何一个订单对象,其订单项集合中不存在这样的订单项,本订单的配送地址所属区域就在该订单项所购商品的禁售区域内。例如,某款“iPhone**”不能卖往长三角地区,如果订单的配送地址是“南京市****”,那么订单里面是不能存在购买“iPhone**”的订单项的。注意,我给出的图13、图17等类图,上面的信息没有冗余。面对冗余,建模者毫无察觉,反而甘之如饴,这说明建模者缺乏基本的建模训练,这已经和面向对象或不变式没关系,即使辩解说“我用的不是面向对象,用的是领域驱动设计大模型人工智能数智化敏捷方法”也一样的。
难怪,我一直“赞扬”很多DDD伪创新:投资少、见效快、产量高、门槛低、仪式感十足,它们确实迎合了某些开发人员的需要。
很可能有人会以“性能”为理由为自己的能力缺乏辩护。是的,如果在实现时确实由于需要频繁计算订单总价,导致出现不可调和的性能问题,可以添加上冗余属性(用数据库语言就是冗余字段)“总价”,甚至添加冗余类(用数据库语言就是冗余表),但这属于通过添加冗余来缓解性能问题的实现套路,和具体的领域知识无关。关于“性能”这块遮羞布,可以参考《软件方法》中的内容。