深圳幻海软件技术有限公司 欢迎您!

DDD的哲学:核心域、统一语言

2023-02-28

作者|钟敬阅读本系列文章:《​​DDD的哲学:模型的关联、演进和认知​​》核心域与主要矛盾大约公元前800年至前200年间,中国、希腊、印度和以色列的文明几乎在同一时期兴起,这被称为人类文明的轴心时代。不同文明展现出了不同的风貌。中国古代文化强调辩证逻辑,重视变化、联系和综合的思维方式,同时又有“子

作者 | 钟敬

阅读本系列文章:《​​DDD的哲学:模型的关联、演进和认知​​》

核心域与主要矛盾

大约公元前800年至前200年间,中国、希腊、印度和以色列的文明几乎在同一时期兴起,这被称为人类文明的轴心时代。不同文明展现出了不同的风貌。中国古代文化强调辩证逻辑,重视变化、联系和综合的思维方式,同时又有“子不语怪力乱神,六合之外存而不论”的唯物主义倾向。古希腊则重视严格的推理和分析,孕育了形式逻辑和公理化的数学体系。印度在婆罗门和佛教哲学中,从另一个方向将辩证法、逻辑和语言学推向极致。以色列的先知则创立了一神论的犹太教和政教合一的社会体制。

直到近代,西方才产生了系统的辩证法体系,集大成者是黑格尔。马克思和恩格斯吸收了黑格尔的辩证法,扬弃了其中的唯心主义成分,形成了辩证唯物主义,成为了风起云涌的无产阶级革命的理论基础。后来唯物辩证法传入中国,与中国传统文化中的辩证法和唯物主义思想一拍即合。为了将马克思主义中国化,毛老师写出了《实践论》和《矛盾论》。前文提到了《矛盾论》第一部分“两种宇宙观”,这一节看一下第四部分“主要的矛盾和主要的矛盾方面”。

事物中包含着多种矛盾,其发展变化成为事物发展的内在动力。在多种矛盾中,一般会有一种占主导地位,称为“主要矛盾”。要有效地处理问题,就要抓住主要矛盾。

例如某筹备开业的保险公司要开发一个保险核心系统。在开始时,“快速开展新单业务的需要与落后的新单处理能力”的矛盾就是主要矛盾,而“快速理赔的要求与落后的理赔能力”之间的矛盾则是次要矛盾。这是因为,新开业的保险公司首先面临的是新单业务的压力而不是理赔的压力。

在领域驱动设计中,领域模型中包含主要矛盾的部分,称为“核心域”(Core Domain)。在复杂的领域模型中,将核心域识别并有效处理的过程称为“精炼”(Distillation, 《领域驱动设计》第15章)。领域驱动设计中还提供了“领域愿景说明”(Domain Vision Statement),“强调核心” (Highlighted Core), “分离核心”(Segregated Core)等模式用于精炼过程的具体实践。

随着内因和外因的变化,主要矛盾和次要矛盾会发生转化。例如,当处理新单业务的系统成熟后,就成为了次要矛盾,而理赔则成为主要矛盾。当理赔也成熟后,灵活的产品和算法的定义可能变为主要矛盾。

总之,以矛盾的观点看问题,就可以知道识别和处理“核心域”的必要性,同时以发展的眼光看待核心域的转化。

这里还有一个微妙的问题:目前业界不同人所说的“核心域”的含义是有区别的。假如我们把业务需求作为“问题空间”,将软件系统的分析和设计作为“解空间”,那么《领域驱动设计》一书中所说的核心域实际上处于解空间,而另一本《实现领域驱动设计》中的核心域说的是问题空间。

《领域驱动设计》中,核心域在“精炼”一章中。其逻辑在于,先有领域模型,然后在领域模型发展到一定的复杂程度时,就需要从中“精炼”出核心域了。由于领域模型处于解空间,因此《领域驱动设计》中的“核心域”谈的是解空间的问题。

而《实现领域驱动设计》中的核心域则来自于对业务问题的分解,因此属于问题空间,先于领域模型的建立。

存在两个层面的“核心域”的现象具有一定的合理性。因为矛盾是普遍存在的,既存在于问题空间,也存在于解空间,两者都有“主要矛盾”,所以可以说两个层面都存在核心域。

目前多数人理解的核心域是《实现领域驱动设计》中的说法,而《领域驱动设计》原书中的模式和实践却没有得到重视。领域驱动设计本来要求“统一语言”,但“核心域”这一概念自身已经不统一了。这是业界需要解决的一个问题。

下一节就来看看与统一语言相关的问题吧。

统一语言与哲学的“语言转向”

前面介绍了哲学从本体论向方法论的转变。在方法论方面,洛克、休谟、帕斯卡等等大家宛如哲学天空中的星斗,而黑格尔和康德是其中最璀璨的两颗。

然而新的问题来了。这些哲学家的著作中往往充斥着晦涩的词汇,其中很多是自造的,在此基础上又进行了复杂的推演,然后产生了更加晦涩的语言。这些莫衷一是的概念又被后人按照自己的意思做了不同的解释。后果是,不仅普通人难以窥其门径,即便专业的哲学工作者,也会争论不休。尽管由此产生了大量论文和“学术成果”,但人们也逐渐意识到,很多争论仅仅是由于对同一名词的理解不同,而没有解决任何实际问题。这样的哲学除了在象牙塔中孤芳自赏,又有多少实际意义?

到了十九世纪末,人们逐渐意识到问题很可能出在“语言”上,很多哲学问题来源于对语言的误用。解决了语言问题,就解决了哲学问题。由此产生了“语言哲学”,并成为了二十世纪上半叶英美哲学的主流。如果说认识论是“对思考的思考”,那么语言哲学就是“对言说的言说”。尽管历史上有很多哲学家也很重视语言,但那时语言只是研究哲学的工具;而现在,语言成了哲学本身。这种哲学关注点的转移,称为“哲学的语言转向”(the linguistic turn in philosophy)。

信息技术和语言有着不解之缘。当您初次学习编程,知道了一条条指挥计算机工作的指令被称为计算机“语言”时,是否曾像我当初那样,有过一丝的惊讶?

在领域驱动设计中和语言直接相关的模式是“统一语言”(Ubiquitous Language)。该模式出现在《领域驱动设计》的第二章,属于该书第一部分“运用领域模型”,与“消化知识”和“模型驱动设计”共同作为全书的总纲。

“统一语言”要求业务和开发人员要用一致的语言来沟通。例如,同一个概念应该用同一个词汇,同一词汇也仅表达唯一的概念。这一道理是如此“直白”,为什么领域驱动设计还要专门强调呢?因为这一实践看似不难理解,但要在实践中真正做好则不太容易。下面我们从语言哲学的角度看一看软件开发中有关语言的问题。

首先,语言哲学认为,语言的意义是在使用的过程中体现出来的。

前面提到的早期哲学家,他们的语言是为了表达自己的观念自造的,并没有真正被用来解决实际问题,因而没有“恰当”地使用语言,从而造成了新的哲学问题。

回到软件开发,如果对软件的理解只是写在文档中,而这些文档很少有机会被阅读,没有真正起到交流的作用,那么也可以看作对语言的误用。这也正是敏捷软件开发所反对的。而统一语言强调不同干系人,尤其是业务人员和开发人员间的沟通。只有在协作中,统一语言才有意义。

近年流行的“事件风暴”方法,也可看作是统一语言的应用。事件风暴和经典的用例分析方法所解决的问题其实是类似的,都是为了捕获行为需求,为进一步的领域建模奠定基础。事件风暴中的“风暴”来源于头脑风暴,是常用的沟通协作方法。因此,沟通协作是事件风暴的内在组成部分,这一点是事件风暴优于传统的用例分析的地方。

其次,语言哲学强调语言的表达能力是有的限度的。

如维特根斯坦所说,“凡可说的,皆可说清;凡不可说的,应保持缄默”。前面提到的早期哲学家的另一个问题,就是强行用语言表达无法表达的东西。

领域驱动设计认为,软件开发的过程可以看作知识的学习、构建和运用的过程。而有些知识是语言难以表达的。不过,难以表达不代表不可知。比如说,我们难以向没有吃过梨子的人描述梨子的味道,但这并不代表梨子的味道是不可知的,只要尝一下就可以了。软件开发中的不可言说知识同样不是玄学,而完全可以通过持续的交流和实践来掌握。因此,不要期望通过文档可以表达所有知识,而要回归到干系人之间的沟通和协作。

再次,语言哲学认为,语言只有在特定的语境(Context)中才有意义。

前文已经提到,领域驱动设计中利用限界上下文(Bounded Context)来保证概念的一致性。而统一语言是针对限界上下文而言的,只有在其所处的上下文中才有意义。英文Context在中文中有两种译法:“语境”和“上下文”。目前《领域驱动设计》一书中采用了“上下文”的译法。其实译作“语境”可能更恰当,在“语境”中运用“语言”,意思会更协调一些。

最后,语言哲学又可分成两大流派:较早的“理想语言学派”和晚一些的“日常语言学派”。

理想语言学派认为自然语言是模糊的,哲学的出路在于利用数理逻辑创造一种形式化的语言。这种语言不仅表意明确,而且可以跨越各种自然语言。这在后来导致了计算机科学的基础理论“形式语言与自动机”的产生。

这一过程可以追溯到十七至十八世纪,莱布尼茨发明的二进制以及提出的“通用文字”(characteristica universalis)的构想。到了十九世纪,布尔利用二进制使逻辑成为了数学演算。布尔代数至今仍然是计算机科学的基础。稍后,现代逻辑学之父弗雷格提出了一阶谓词演算。这一理论也导致了像Prolog这样的逻辑编程语言的诞生。二十世纪,罗素、哥德尔等人进一步将数理逻辑发扬光大。后来,图灵提出的“图灵机”,丘奇提出的lambda演算,成为了算法和编程语言的数学基础。我们程序员所使用的各种编程语言,也可以算作形式语言。

较晚出现的一派则认为自然语言本身就是精确的,之所以会让人觉得模糊,是因为没有进入特定的语境。因此,应通过对自然语言本身的研究处理哲学问题,而无需借助形式语言。这就是“日常语言学派”。

对于软件开发者来说,没有必要陷入哲学家的争论。我们反而可以看到两者各自的合理性,以及互相衔接的可能。

一方面,计算机中运行的是二进制的字节流,而这又是由编程语言编译得来的。机器语言和编程语言都是形式语言。另一方面,计算机所要解决的业务问题,却开始于日常语言或者说自然语言的描述。因此我们必须有一种自然语言和形式语言的转换机制。领域驱动设计中的领域建模正是这一机制的重要环节。领域模型在自然语言和编程语言之间建立起了桥梁,帮助跨越两者之间的巨大鸿沟,成为开发复杂软件的重要手段。

领域驱动设计中,常用UML(统一建模语言)进行领域建模。严格地说,UML也是一种形式语言。不过比起编程语言,UML更贴近业务概念,更容易与自然语言转换。同时,用UML建立的领域模型,又可以方便地映射到编程语言和数据库设计。UML常用于软件系统的分析和设计,而编程语言用于编码。在分析、设计、编码过程中保持语言的统一,是统一语言的另一层意思。

小结

我们用若干章节探讨了领域驱动设计的哲学内涵。两者之间的这种契合关系并非偶然。

哲学的主要目的是解决对世界进行认识(认识论)和诠释(语言哲学)问题。前面说的本体论,从现在的观点来看也可以理解为一种对世界的诠释。

计算机系统本质上是用软硬件对现实中的事物以及逻辑进行模拟,进而解决现实中的问题。计算机软件可以看作现实世界的模型。在计算机中最终运行的模型是二进制流,这在形式上与现实世界有巨大的差距,这是软件开发的本质复杂性之一。

为了跨越现实世界和二进制模型之间的鸿沟,软件开发方法学采取了渐进的方式。首先将现实世界映射为分析模型,然后由分析模型映射为设计模型,再映射为编程语言模型,最后编译成二进制模型。通过这种逐层递进的方式,来化解软件开发的困难。

领域驱动设计中所说的领域模型,主要指上面说的分析模型,目的是为了反应业务概念,也兼顾了一定的开发视角。建立领域模型的过程,就是认识现实世界中的概念及其发展变化逻辑的过程,而这正是认识论和语言哲学的应用场景。这就是哲学对领域建模以及整个软件开发过程具有指导作用的原因。

对领域建模的强调,并非始自领域驱动设计,而是从软件开发方法学出现的时候就已经开始了。早期的结构化方法学,采用数据流图和ER图等方式进行建模。后来发展为面向对象方法学,采用对象建模的方式。领域驱动设计则是对面向对象方法学的归纳和发展,为面向对象方法学建立了一套模式语言,使之更容易学习和运用。

领域驱动设计主要面向的还是传统意义上的软件开发,尤其是企业应用的开发。经过几十年的发展,至少在理论上已经研究得比较透彻了,难点在于工程上的推广和应用。而近年来,人工智能、大数据以及下一代互联网的发展,带来了一系列新的问题:隐私如何保护?谁拥有个人行为数据的所有权?人工智能是否会剥夺人的自由意志?怎样判定人工智能作恶的法律责任?机器人可以用于战场吗?个人怎样面对高科技所带来的焦虑?等等。这些则是伦理学、心灵哲学所要讨论的问题。这些话题超出了领域驱动设计的范围,我们的讨论,也可告一段落了。