作者 | 钟敬
接上篇《当我们谈论DDD时我们在谈论什么》
“关联”、《矛盾论》、毕达哥拉斯学派
DDD的哲学意味(上)说到了“模型驱动的设计”以及其中两个重要的模式“实体”和“值对象”,两者统称“领域对象”。在领域建模的过程中,建立领域对象间的“关联(Association)”也是非常重要的。《DDD》第5.1节对此进行了专门的讨论。不过与实体不同,艾老师并没有把关联当做一种正式的“模式”。这一点实属可惜,因为关联至少与实体有同样的重要性。为什么这么说呢?下面还是先扯几句哲学。
前面提到毛老师的《实践论》,这里再说说怹老人家的另一篇杰作《矛盾论》。这篇论文的第一部分“两种宇宙观”中提到了两种认识论体系:“形而上学”和“唯物辩证法”。前者是错误的、后者是正确的。
形而上学认为事物的发展是静态的、外因驱动的、孤立的;唯物辩证法则认为事物的发展是动态的、内因驱动的、联系的(还记得中学政治课背过吗?)。前两点我们后面再聊,这里先讨论“联系”。
《矛盾论》中引用列宁的话:“要真正地认识事物,就必须把握住、研究清楚它的一切方面、一切联系和‘中介’。我们永远也不会完全做到这一点,但是,全面性这一要求可以使我们防止犯错误和防止僵化。”这强调了,只有充分了解事物之间的联系,才能充分认识事物。
DDD中,领域(事物)的概念以实体、值对象、聚合、模块等方式表达出来。有些伙伴把领域中的主要聚合或实体识别出来后,却没有识别它们之间的关联,就认为已经完成了领域建模。这样的模型其实是不完整的。那么,如果我们认识到了这一点,并且识别出了关联之后,还要进一步识别关联上的哪些信息呢?让我们先看一下古老的毕达哥拉斯学派。
这个学派在古希腊最早对数学进行了系统的研究。他们发现了无理数,然后把泄露无理数秘密的团队成员扔进了大海。毕达哥拉斯认为世界的本原是“数”。宇宙的和谐来源于数的和谐。而数的和谐体现在十组对立的关系中:一与多、奇与偶、左与右、阴与阳、动与静、曲与直、明与暗、善与恶、方与长、有限与无限。在这十组关系中,最根本的是“一与多”。也就是说,只有把握了“一与多”,才能把握“数”,进而把握世界。
从毕达哥拉斯的学说中,我们或许可以得到这样的启发(嗯 ~ ~ 我承认有点牵强):在领域模型中,对关联进行建模时首先要考虑的是数量关系。也就是常说的“一对一”“一对多”“多对多”等等。如果做得精细些的话,还可以考虑存在性(必选的还是可选的)。不少建模人员在建模时都忽视了理清数量关系,从而限制了对领域知识的深刻理解。
对数量关系的识别看似简单,但我们发现百分之六七十的初学者在实践中都会搞错。只有经过一段时间的练习,才能充分掌握。
此外,前文提过,除了这里说的实例之间的关联以外,还要考虑类型之间的关联(也就是泛化)。对泛化关系的掌握,是领域建模技术从初级阶段迈向中级阶段的门槛,也是向高级建模技术进阶的关键。要熟练掌握这项技术,除了在项目中实践,还可参考《分析模式》。在《DDD》的后半部分有一些高阶的例子运用了泛化,但在前面讲模型驱动设计的章节却没有提及泛化,这就忽略了一种建模的重要手段,也算是该书的一个瑕疵吧。
模型的演进、辩证法、进化论
前文说到,唯物辩证法认为事物的发展是动态的、内因驱动的、联系的;并且已经讨论了“联系”。这一节首先讨论“动态”。
事物的发展变化是永恒的。因此,我们会不断强调,与其期望一次性把领域模型和架构建好,不如建立团队的架构演进能力。在《DDD》第8章至第13章,集中讨论了模型重构的方法。
模型要不断演进,这个说法多数人听了都会点头称是。又有谁不知道“唯一不变的就是变化”呢?然而在这一点上,说到和做到是有距离的。
真想做到模型的演进,不仅需要上述《DDD》中的建模技能,还要扎实地掌握重构、TDD(或者至少是自动化测试)和持续集成,我将之称为敏捷工程实践的“老三样”。此外,还要掌握架构演进和数据库演进的若干种模式,以及建立多维度的指标体系,利用工具进行量化的架构守护等。
然而我们很多人,哪怕接受了DDD,也是急于学习建模技能(这本身没有错),而对模型演进丝毫不感兴趣。这样的DDD往往半途而废。这是因为,如果不能演进模型(进而演进整个软件系统),那么软件和模型就会逐渐不一致,最后模型变成废纸一张,一切打回原形。这背后可以回归到团队甚至企业文化的问题。衡量一个企业的文化,关键不是看怎么说,而是看怎么做。
谈完“动态”,再谈“内因驱动”。
《矛盾论》中强调“唯物辩证法认为外因是变化的条件,内因是变化的根据,外因通过内因而起作用”。内因是第一位的,外因是第二位的。而形而上学只看到外因,外因也能使事物发生变化,但只是量的变化。而内因才能导致质的变化。换句话说,事物本身必然包含内在的矛盾,这一矛盾的发展变化,在一定外因的条件下,会导致事物发生质的变化。
联系到模型的演进,我们从《DDD》中的相关例子看到,随着对领域知识理解的深入,模型的重构往往不是多了几个实体、少了几个关联,而是多出了若干抽象层次,甚至将模型的核心部分打碎重组。所以我们既要对这一点有充分的心里准备,知道这是复杂系统的模型演进中必然发生的;也要未雨绸缪,掌握相关的建模和架构演进技术。不过也不用怕,当掌握相关技能后,再复杂的重构,总可以小步快跑,稳健推进。
前面反复提到了架构演进。其实《DDD》和《演进式架构》是两本书。两者的侧重点不同,一本侧重领域建模,一本侧重系统架构演进。不过在实践中我们常常将两者结合起来运用。下面聊两句演进式架构的原理,这超出了《DDD》原书的范围。
“演进式架构”强调增量地、多维度地、导向性地架构变化过程。我们主要解释这三个关键词。
《演进式架构》(Building Evolutionary Architectures)和“进化论”(Theory of Evolution)中的“演进”和“进化”其实是同一个英文词根。有人将“Theroy of Evolution”翻译成“进化论”,有人翻译成“演化论”。《演进式架构》的译者讨了个巧,各取一个字,就成了“演进”,其实都是一个意思。所以,演进式架构实际上引用了进化论的隐喻。
达尔文的《物种起源》将进化论总结为遗传、变异和适者生存。该书第一部分首先讲的是“人工选择”,例如对狗和金鱼的驯化;后面才推广到自然选择。将演进式架构和进化论进行比较,可以看到一些有趣的共同点。
将软件系统类比为生命体。不影响系统架构的渐进式修改相当于生物的“遗传”。对架构的本质性修改相当于生物的“变异”。变异的系统需要经过各种验证,看看是否符合人的需求,这实际上是一种“人工选择”的过程。适应需求的架构得以延续,不适应的则会被淘汰或进一步变异,这就是演进式架构中说的“导向性”,也这就是“适者生存”。达尔文指出物种的进化总是渐进式的,而不会短时间内发生剧烈变化。这就是演进式架构中的“增量式”变化。生物适应环境时,要适应多方面的因素:捕食能力、逃避天敌的能力、适应气候的能力、抵御病虫害的能力、繁殖能力等等。各种因素都适应,才能生存。同样,系统也要适应多方面的因素:功能的正确性、可扩展性、可维护性、性能、安全等等。这就是演进式架构中说的“多维度”。
上述类比可以促使我们对演进式架构进行更深入的思考。至于具体技术,可以参阅原书。
限界上下文与人类认识能力的有限性
虽然哲学家可能是人类有史以来最喜欢争辩的群体,但有一个观点,多数哲学家却是有共识的:人的认识能力是有限的。从孟子的“吾生也有涯,而知也无涯”,到康德的“物自体不可知”,再到辩证唯物主义的“人只能认识相对真理”,都说明了这一点。那么,这和限界上下文有什么关系呢?
如果问一位同事限界上下文是什么,常常听到这样的回答:“业务功能的边界”“业务领域的边界”诸如此类。这种说法虽然没错,但说的是结果,不是原因;是表象,不是本质。因为聚合、模块等也可以说是一种“业务功能的边界”。所以上述回答没有答到点子上。
限界上下文是在《DDD》第14章“保持模型的完整性”中介绍的。眼尖的同学可能就会问了:为什么这一章的名字不叫做“划分功能边界”?这里的“完整性”到底指什么?
让我们继续看原文。这一章的开头几段所举的例子,是说有两个团队共用了一个Charge(收费)对象。但两个团队所说的Charge背后的业务概念其实是有差别的。开始时两个团队都没有意识到这个问题。他们都去改这个类,后来程序崩溃了。然后作者说“模型最基本的要求是它应该保持内部一致,术语总具有相同的意义,并且不包含互相矛盾的规则……”。所以,作者在本章要解决的实际上是系统的一致性问题。
这一章标题中的“完整性”是对单词Integrity的翻译。Integrity固然有“完整性”的意思,但也包含“统一”、“健全”等含义。因此,本章的标题如果翻译成“保持模型的统一”会更准确。翻译成“完整性”则容易造成误导。
保持系统的一致性,早已被IT界的前辈们所重视。例如,Brooks老师在七十年代写的《人月神话》中已经做了强调。不过,之前的专家们在潜意识里总是认为,对于一个复杂的系统,总有办法做到团队理解的全局一致性。《人月神话》中就列举了若干种方法。
然而《DDD》中指出“在理想世界中,我们可以得到涵盖整个企业领域的单一模型。这个模型将是统一的,没有任何相互矛盾或相互重叠的术语定义”然而“大型系统领域模型的完全统一既不可行,也不划算”。
为什么“不可行”呢?答案就是人类认识能力的有限性。由于大型软件是团队协作开发的,因此这里的认识能力还要扩展到群体的认识能力。也就是包含了个人的认识能力、群体的协作能力、以及两者的相互作用。越是复杂的系统,认知起来越困难。当系统复杂性达到一定程度时,就超过了一个团队的认知能力,无法保证系统的一致性了。
解决的办法就是分而治之,将一个“理想中”的大模型划分成几个小模型,每个小模型不超过团队的认知能力。因此每个小模型内部就可以保证严格的一致性,而与另一个模型内部不需要一致,只需要约定好模型之间的接口就可以了。也就是说,我们在了解了人类认识能力的有限性之后,不再追求全局一致性,而是退而求其次,代之以局部的一致性,从而使系统的正确性在总体上得到管理,实现业务目标,这样就足够了。
概念的一致性是通过语义上的一致性来表达的,所以作者引用了语言学上“上下文”的术语。在日常语言中,人们常常会随时切换上下文,在特定场景下未必会影响沟通。但在计算机软件中,一个元素要么属于一个上下文,要么不属于,这个界限必须清清楚楚。为了强调这一边界的重要性,因此称为“限界上下文”。
上下文的具体划分方法,仍然要围绕领域概念的内聚和耦合关系。因此划分的结果,当然表现了一个“领域的边界”或者说“功能的边界”。如上文所述,这是结果而不是原因。
根据敏捷组织的理论,一个开发组的规模最好在5~9人之间(这是有心理学研究作为依据的)。那么,一个限界上下文的规模,一般就是这样一个开发组所能把握的规模。
知道了人类认识能力的局限,就使我们对这个世界秉持谦卑和敬畏的态度。这也有助于我们理解限界上下文所要解决的问题、划分的方法、及其与团队的关系。最终使我们能够正确划分上下文,管控好复杂系统的一致性。
在这个系列文章的最后一篇,我们将继续讨论“核心域”、“统一语言”,并对全文做一个总结。