天天看点

评张逸的“状态和事件本质相同”(上)-DDD话语批评之一

有位同学给我发了一页张逸的书,让我评点一下其中观点。

评张逸的“状态和事件本质相同”(上)-DDD话语批评之一

图1 摘自《解构领域驱动设计》(张逸,2021)

图片中的“状态和事件本质上是相同的”真是令我“耳目一新”。那就针对这页书的内容来讲讲吧。

我先讲一讲状态机的一些知识点,然后根据知识点来评价一下张逸这页书的内容。

一、状态是描述某个类的“形容词”

状态的名称和类的名称凑到一起,“状态的类”或“这个类是状态的”应该能说得通。

(也可以把整个系统当成一个类来描述状态,这时得到的状态机相当于系统的需求规约,这样的状态机往往是非常庞大的。)

例如,针对“人”这个类,描述它的形容词可以有:高、矮、胖、瘦、贫、富、美、丑……等。“高的人”、“美的人”、“这个人是高的”、“这个人是美的”是可以说得通的,这些都可以作为“人”的状态。

有的“形容词”是动词变化而来的,例如,“健身”是动词,但“正在健身的”、“已健身的”就变成了形容词,“正在健身的人”可以说得通。

我们看英文书籍中的状态机图,往往可以看到很多名称中带有“ing”、“ed”的状态,就是现在分词、过去分词作为形容词使用。“domain-driven”就属于这种情况,说domain-driven(定语) design或说this design is domain-driven(表语)是可以的。

图2是状态图。节点是状态,形容词;边是事件/动作,动词。

评张逸的“状态和事件本质相同”(上)-DDD话语批评之一

图2 状态图是这样的

光是这一点,不少网络上的“状态图”(statechart)、“状态机图”(state machine diagram)就已经趴下了。有的文章说“***是一张状态(机)图”,结果一看所给出的图上的节点,动词!这分明是活动图(或流程图、数据流图)嘛。

例如,下面这张图3,左上角说是描述复合状态(Compound States),但节点却是动词,这是错误的。

评张逸的“状态和事件本质相同”(上)-DDD话语批评之一

图3 某绘图工具给出的示例(摘自网络)

节点是动词,那是活动图,如图4。活动图的边隐含着对象(或数据)流,名词。

评张逸的“状态和事件本质相同”(上)-DDD话语批评之一

图4 活动图是这样的

根据以上知识点,我们来看一下张逸书页中的观点。图5是图1的一部分,我特地圈出了要评论的内容。

评张逸的“状态和事件本质相同”(上)-DDD话语批评之一

图5 图1的一部分,加了标注

圈出的地方,张逸的陈述如下:

①状态和事件本质是相同的,虽然UML状态图没有把状态视为事件。

②状态就是领域事件。

③领域事件的命名是动词的过去时态。

我的评论

张逸的陈述①相当于认为状态机的数学模型(如图6)中的Σ和S是一个东西,这个“创新”要是成立,整个理论体系都要推翻重来了。至于为什么张逸会有这样的认识,后面的段落还会继续深挖其中的可能原因。

评张逸的“状态和事件本质相同”(上)-DDD话语批评之一

图6 有限状态机的数学模型,摘自wikipedia

张逸的陈述①说的事件是UML状态机中的事件,并没有直说这个事件就是所谓的“领域事件”。也许“领域事件”和“事件”还不一样,陈述②说的“状态就是领域事件”没准就是对的呢?

说鹿是马不合适,要是我定义我这个鹿是“领域鹿”,然后说它其实就是马,也不是不可以,对吧?

那我们来看看这个“领域鹿”,不,“领域事件”是什么,然后就从本小节提到的知识点——词性来说一说张逸的陈述②和③。

“Domain Event”这个词不是DDD圈子首先用的,例如图7所示的这篇1999年的文章,就使用了domain events的说法。文中的information base用面向对象的术语来说就是“类和关系”。

评张逸的“状态和事件本质相同”(上)-DDD话语批评之一

图7 摘自Action Inventory for a Knowledge-Based Colloquium Agent(Erik Sandewall, 1999)

当然,DDD圈子可以自行定义这个词。以下是Martin Fowler的定义:

评张逸的“状态和事件本质相同”(上)-DDD话语批评之一

图8 摘自https://martinfowler.com/eaaDev/DomainEvent.html

从Fowler的陈述和所给类图可以知道,领域事件实际上就是一个“行为记录”类,像录像机一样,把发生过的事情的一些细节记下来。就是这么一个东西,没有必要过度渲染,活生生搞成玄学。

Fowler加了一个限定“affects the domain”,也就是说,不是什么都记,影响领域的才记。“影响领域”是一个模糊的说法,后面Fowler又补充得更精确一些:“can trigger a change to the state of the application(可以触发应用的状态变化)”。

以下是Greg Young的说法:

评张逸的“状态和事件本质相同”(上)-DDD话语批评之一

图9 摘自http://codebetter.com/gregyoung/2010/04/11/what-is-a-domain-event/

从Greg Young的说法可以得知,DDD话语中,领域事件的命名是动词的过去式,张逸的陈述③是没错的。就拿“domain-driven”来说,领域事件命名应该是“domain-drove”。

但是,动词的过去式还是动词,说的是瞬间的行为,不是形容词,不能用来做定语或表语,不能作为状态的名称。

do的过去式是did,不能作为形容词,可以作为形容词使用的是“to do”、“doing”、“done”,这也是我们常见到的状态的名称。

张逸可能混淆了“过去式”和“过去分词”(完成态),混淆了行为和状态。did是一个行为,瞬间发生就结束,done是一个状态,系统可以停留在那里很久。

规则动词的过去式和过去分词后面都是ed,也许正是这一点让张逸认为这两个ed是一回事,从而得出结论“状态就是领域事件”。碰到不规则动词,这个问题就暴露出来了。

如果把领域事件理解成“行为记录”而不是“行为”,那么这个动词其实是名词。例如,“我的奋斗”、“嫌疑人X的献身”以及“领域驱动设计”就是动词的名词化。

Fowler和Young都没有说“状态就是领域事件”。Fowler只是说领域事件触发状态的变化。

事件风暴(我重点批评的伪创新之一)的“发明”者,Alberto Brandolini在他的书中,也说:

评张逸的“状态和事件本质相同”(上)-DDD话语批评之一

图10 摘自 Introducing EventStorming(Alberto Brandolini,2018)

从Brandolini的陈述我们知道,他也认为领域事件用动词的过去式命名,另外他还提到“Domain Events as state transitions(领域事件作为状态迁移)”,这个说法和Fowler类似。

由此我们得知:

(1)DDD话语里面的Domain Event的命名确实是过去式。

(2)所列这几位没有说Domain Event相当于状态,最多说了相当于状态迁移。

(3)“状态和事件本质是相同的”,“状态就是领域事件”的说法可能是张逸自己加上去的。

张逸当然有资格发展出自己的东西,但最好在了解已有知识的基础上再发展,否则容易陷入“伪创新”。

张逸为什么要这样说呢?表面上的原因似乎是上面说的:

(1)他混淆了过去式和过去分词。

(2)他混淆了状态和迁移。

但问题并没有那么简单。

假如张逸退一步,不说“状态和事件本质是相同的”,“状态就是领域事件”,改口说“领域事件和状态是一一对应的,把事件的名称变换个形式就是状态了”,例如“did→done”,“broke→broken”,那可以吗?

依然是不对的!这也许就是导致张逸认为“状态和事件本质是相同的”的本质原因。

因为

二、状态和事件不是一一对应的

虽然现在分词、过去分词这样的“形容词”可以作为状态的名称,但并非状态的优选名称。

就拿人的例子来说,一个人发生了“健身”的行为,他可能有什么状态变化?

可能有的人会像图11那样,说状态为“未健身”、“已健身”:

评张逸的“状态和事件本质相同”(上)-DDD话语批评之一

图11 不合适的状态

“未健身”、“已健身”作为状态并非不可以,但外部的对象可能并不在意这个人是否健身,在意的可能是“美”和“丑”,如图12。

评张逸的“状态和事件本质相同”(上)-DDD话语批评之一

图12 更合适的状态

而要从丑到美,还有其他的迁移路线,如图13,多个事件可以触发到同一状态的迁移。

评张逸的“状态和事件本质相同”(上)-DDD话语批评之一

图13 事件和状态不是一一对应

或者看“技术”一点的例子,栈(Stack)。事件是压入(Push)和弹出(Pop),但我们谈论栈的状态时,显然不是像图14那样:

评张逸的“状态和事件本质相同”(上)-DDD话语批评之一

图14 不合适的栈状态

更合适的栈状态如图15:

评张逸的“状态和事件本质相同”(上)-DDD话语批评之一

图15 合适的栈状态

为突出重点,以上状态机图只保留了迁移的事件,忽略了警戒条件、动作等内容。

从图15可以看出,要迁移到“半满”状态,触发的事件可以是“压入”,也可以是“弹出”;而“压入”事件,可能会导致迁移到“半满”,也可能会导致迁移到“满”。状态和事件不是一一对应的。

再看图16的交通灯例子,状态三个,事件就一个Timer_Tick。啥,“转黄”、“转绿”等行为在哪里?藏在各个状态的入口动作中。

评张逸的“状态和事件本质相同”(上)-DDD话语批评之一

图16 交通灯的状态

说到这里,我们再来看看张逸的陈述。

评张逸的“状态和事件本质相同”(上)-DDD话语批评之一

图17 图1的部分,加了标注

张逸的陈述④解释了为什么他认为“状态和事件本质相同”,原因之一是“它们都是某个行为产生的结果,并与该行为相关联”。

我的评论

这中间的逻辑是不成立的。

炼钢既产出钢,也产生废渣。那能不能这样推论:钢和废渣都是某个行为产生的结果,所以这二者的本质相同?如图18。

评张逸的“状态和事件本质相同”(上)-DDD话语批评之一

图18 钢锭=废渣?

更深入地剖析背后的原因,可能是混淆了泛化和关联的区别。

我以人为例画出类图,如图19。图中人和大脑、阑尾的关联可以改为更贴切的组合型关联。

评张逸的“状态和事件本质相同”(上)-DDD话语批评之一

图19 泛化和关联的区别

人有男人和女人,说的是集合关系,也就是泛化关系,说男人、女人都是人,本质相同,这个可以。

人有大脑和阑尾,说的是个体关系,也就是关联关系,说大脑、阑尾都属于人,本质相同,这个就有问题了。

这可能就是张逸认为“状态和事件本质相同”,“它们都是某个行为产生的结果,并与该行为相关联”背后的原因。

我在以前写的一篇文章中就指出过滕云 译、张逸 审的《实现领域驱动设计》中译本在翻译时搞混泛化和关联的问题:猴子掰玉米?比较不同版《领域驱动设计》说“不变式”和“聚合”。

那么,怎样的表述是正确的呢?

正确的表述应该是:对象上发生某事件,可能会导致新增一个对象来记录此事件的内容,可能会引起状态变化。

我把Fowler给出的类图翻成中文,如图20:

评张逸的“状态和事件本质相同”(上)-DDD话语批评之一

图20 Fowler在图8给出的类图,翻译成中文版

再画一张序列图,如图21。

评张逸的“状态和事件本质相同”(上)-DDD话语批评之一

图21 事件发生的序列图

注意我上面陈述中的“可能”和图21中的opt。

(1)有事件发生,未必需要记录事件

电梯每天上上下下,不知发生多少次“召唤”事件,但是目前的电梯不会记录“召唤”事件的细节——谁召唤的、什么时候召唤的……,当然,也许有一天,电梯有了足够的计算和存储资源,就会记录这一切。

不记录事件,不代表事件没发生,更不代表事件没有产生效果。

(2)有事件发生,未必会引起状态变化

以图15的栈为例,假设栈的长度是1000,“空”状态下发生“压入”,迁移到“半满”,再发生“压入”,迁移到“满”的警戒条件没满足,状态并没有变化,依然停留在“半满”。

可能有人会就说,那是你的状态不合适,如果把“未健身”、“已健身”、“未压入”、“已压入”作为状态,搞一一对应,不就好了嘛?

哎,有的人就会炮制一些一一对应的“方法学”,然后兜售给需要的人。这些“方法学”的优点是简单易学,不用思考,产出巨大,是摸鱼的上佳选择。

一一对应的招数可以是:

(1)为每个属性值分配一个状态

还是以栈为例,如图22。

评张逸的“状态和事件本质相同”(上)-DDD话语批评之一

图22 一个属性值一个状态

如果是图22这样,那就确实满足“有事件发生,就有状态变化”了。

(2)去往各个状态的迁移对应各自的单个事件

这应该就是张逸所想象的状态机,也是许多“事件风暴”得出来的状态机(虽然他们未必画图)。如图23,去往A-ed的迁移只能由A事件触发,去往B-ed的迁移只能由B事件触发……

评张逸的“状态和事件本质相同”(上)-DDD话语批评之一

图23 一个状态对应一个事件

图23这样的状态机是存在的,例如“报告”的“已受理”、“已初审”、“已复审”、“已终审”。

但如果领域逻辑真的是如此简单而直接,用不用状态机来整理领域逻辑都无所谓。

而且,逻辑往往没有那么简单。一个undo事件就可以破坏这个一一对应,它可以让对象从“已复审”迁移到“已初审”,也可以让对象从“已初审”迁移到“已受理”。

那废除undo事件不行吗?只保留A、B、C,让调用者来决定什么时候A,什么时候B,什么时候C。

如果是这样,不如用下面这个更绝的一一对应:

(3)只保留“改变状态”事件

如图24,调用者通过调用“改变状态”来让对象改变状态,爱怎么改怎么改。

评张逸的“状态和事件本质相同”(上)-DDD话语批评之一

图24 只保留“改变状态”事件

你看,表面上我有状态机(高大上!),但又不用做太多思考,受用,爽!

但是,这样的“状态机”是没用的。

为什么状态应该是这些而不是那些,事件应该是这些而不是那些?我们就要了解下面的知识:

三、状态机到底是干什么用的

待续……

[2020.01加一套题]UMLChina建模竞赛题大全-题目全文+分卷自测(11套110题)

全程字幕-25套UML+Enterprise Architect/StarUML建模示范视频

[新增:鸵鸟]软件开发团队的脓包:皇帝的新装、口号党、鸵鸟、废话迷

《软件方法》书中自测题-题目全文+分卷自测(1-8章)16套111题

怪论:东北公司用用例做需求,反映了东北互联网落后?

别把洋垃圾当宝贝-评InfoQ中国“敏捷……”文章(一)

中文书籍中对《人月神话》的引用(完结,共110本):软件工程通史1930-2019、实用Common Lisp编程……

CTO也糊涂的常用术语:功能模块、业务架构、用户需求……[20210217更新]

UMLChina服务介绍

评张逸的“状态和事件本质相同”(上)-DDD话语批评之一

继续阅读