你的代码真的正确实现了领域模型吗?从领域驱动设计从业者的角度来看,这个话题有些模糊。代码?域模型?按照EricEvans的书《Domain-Driven Design》,代码本身不也是一种领域模型吗?在开始本主题之前,有必要简单梳理一下相关概念。EricEvans认为领域模型本身并不是一个特殊的图,而是这个图所传达的知识,这也是对领域专家头脑中的知识进行严格组织和选择性抽象。基于这种理解,领域模型可以自由地表示为图表、文本甚至代码!换句话说,领域模型和代码在埃文斯看来本质上是一回事。然而,本文使用“域模型”作为概念而不是“代码”。为什么?这是因为这种理解往往更符合开发者的认知。从大多数开发人员的角度来看,“模型”的概念,无论是否面向“领域”,通常都与代码区分开来,并以图表(例如,典型的UML图)的形式存在。其实这两种观点并不冲突。作者在这里将前者埃文斯对领域模型的理解称为“广义领域模型”,后者特指将以图形式存在的领域模型称为“窄领域模型”。广域模型和窄域模型的关系如下图所示。细心的读者会发现,上图传达了另外两个观点。第一:图形形式的领域模型通常对应于设计阶段,而代码形式的领域模型通常对应于实现阶段。这个观点其实和张译的《我对领域模型的理解》文章不谋而合。他认为设计在领域模型上的反映就是设计模型;代码在域模型上的表达就是实现模型。在设计阶段,软件设计者需要根据对领域的理解,建立领域问题的解决方案;在实现阶段,开发人员根据设计模型进行编码和实现,使领域模型形象地展现在代码上。当然,在设计和实现之前,还有一个分析阶段,领域专家和开发团队围绕领域梳理对业务知识的理解。然而,分析阶段关注其他问题。本文主要讨论设计和实现阶段及其软件工件之间的关系。第二:狭义的领域模型和代码都是广义领域模型的具体体现,两者之间需要有明确的对应关系。即代码应该忠实地反映领域模型,(狭义定义的)领域模型也应该及时反映代码的变化。正是领域模型和代码实现之间的紧密联系使模型变得有用,并确保我们所做的深度设计最终可以转化为运行的软件。当然,如果团队本身能力很强,只用代码来表示领域模型,那么代码和领域模型不一致的问题是可以从根本上避免的。言归正传,那么所谓领域模型与代码的“一目了然”指的是什么呢?作者认为,这具体包括三个方面的对应关系:Terminology,即代码中使用的领域术语要与(狭义定义的)领域模型中的保持一致,这与Evans强调的一致在代码中使用通用语言(UbiquitousLanguage)是一致的。领域概念的分解,即结构设计,包括领域对象及其关系,在代码和领域模型上应该保持一致。例如,UML类图和代码结构之间应该有明确的对应关系。领域概念之间的交互,即行为的设计,包括领域对象之间的动作,在代码和领域模型中应该是一致的。例如,UML序列图和代码行为之间应该有明确的对应关系。为了使设计阶段的领域模型与实现阶段的代码保持一致,读者可以从模型驱动工程的角度应用以下策略。01模型到代码的转换模型到代码的转换(Model-to-CodeTransformation)是模型驱动工程的典型技术,在2010年左右尤为活跃。将此技术应用到领域驱动设计中,可以帮助从模型中自动生成代码领域模型,从而减少开发人员手工编写代码的工作量,一定程度上可以减少由于开发人员的疏忽而导致的与领域模型的不一致。另一方面,由于其代码自动生成过程,该技术也有望提高软件开发效率。领域驱动设计社区推崇的“模型到代码转换策略的工具”包括SCULPTOR(http://sculptorgenerator.org/)、LEMMA(https://github.com/SeelabFhdo/lemma)等然而,从模型到代码的转换方法存在两个固有的问题。首先,由于模型本身总是没有代码那么具体,从模型到代码的转换通常只生成一个代码框架,代码实现细节仍然需要开发人员手动实现。这个过程仍然会导致代码偏离领域模型。其次,当开发人员根据生成的代码框架实现代码细节时,领域模型本身也可能发生演变。那么,演化领域模型生成的代码应该如何与开发者正在修改的代码进行合并呢?两者的合并冲突应该如何解决?02Code-to-ModelconversionCode-to-ModelTransformation是模型到代码转换的逆向过程,可以认为是模型驱动逆向工程(Model-DrivenReverseEngineering)的典型技术。将此技术应用于领域驱动设计可以帮助从代码中恢复可视领域模型。恢复的领域模型本身就代表了“代码中实现的领域模型”,也是代码的一种实现。抽象和可视化。使用还原的图形领域模型,开发者可以进一步将设计的领域模型与实现的领域模型进行比较,从而快速发现和报告两者之间的差异。此外,ReflexionModeling还可以在设计的领域模型和实现的领域模型之间进行,即实现的领域模型与设计阶段的差异可以通过可视化映射模型(包括新建、删除和修改元素)来展示,从而更直观地帮助开发人员识别代码是否偏离领域模型。此外,抽象和可视化后的代码视图还可以帮助开发者消化知识(KnowledgeCrunching),减少流程对代码实现细节的依赖,从而降低流程的认知复杂度。该策略的具体实现方法可以参考作者最近发表在《软件学报》上的文章《一种面向领域驱动设计的逆向建模支持方法》(http://www.jos.org.cn/jos/article/abstract/6278?st=搜索)。03模型组合模型组合(ModelComposition)基于关注点分离的思想,将软件系统划分为模型单元,通过综合处理技术将这些单元组装起来,解决系统的复杂性。将该技术应用到领域驱动设计中,通过结合领域模型元素(及其代码),可以得到开发者期望的领域模型和代码,从而减少开发者手工实现代码的工作量,减少代码与领域模型的偏差。在笔者看来,这种策略类似于现在流行的low-code。本质上是利用组件化和模型驱动工程来减少开发者的代码编写。目前,根据最新的学术文献综述(https://www.sciencedirect.com/science/article/pii/S0950584920300689),该策略仍然存在诸多挑战,比如如何自动匹配要组合的模型元素,缺乏04模型和代码的溯源管理模型驱动工程广泛应用于设计文档、源代码、测试用例等多个软件产品之间的溯源管理。将这种技术应用到领域驱动设计中,可以建立领域模型和代码之间的联系,并通过跟踪这些联系直观地展示和维护领域模型和代码之间的一致性。此外,如果辅以自动化的模型转换技术(如从模型到代码的转换),这种机制还可以自动维护领域模型和代码之间的联系。也就是说,当源模型(如领域模型)被修改时,这种修改可以自动传播到目标模型(如代码)。学术界(https://www.sciencedirect.com/science/article/pii/S0950584912001346)认为缺乏适当的追溯信息存储、处理和查询技术是该策略当前的局限性。作者还没有看到这种策略应用于领域驱动设计社区。更重要的是,上述基于模型驱动工程的方法实际上依赖于某种领域特定语言(DomainSpecificLanguage)。例如,为了实现模型到代码的自动转换,所需的领域特定语言需要回答三个问题,即如何表示领域模型、如何表示代码以及如何建立领域与领域之间的映射。模型元素和代码元素。例如,如何在图形域模型中表示聚合?如何在代码中表示聚合?如何在这两种聚合形式之间建立关联?既然如此,这些领域特定语言的使用很可能会对开发团队的建模语言和编程语言施加一定的限制。要在工业界实施这些模型驱动的工程方法,还有很多值得讨论的问题。作者这里列举的一、二、三,只是套路而已。
