Eric Evans认为,领域模型本身并不是某种特殊的图,而是这种图所要传达的知识,并且这还特指针对领域专家头脑中的知识的一种严格的组织且有选择的抽象。
根据这种理解,领域模型可以被自由地表示为图、文字甚至是代码!也就是说,领域模型和代码在Evans看来本质上是同一种东西。
尽管如此,本文还是使用“领域模型”作为一个与“代码”相对的概念,这是为什么呢?
这是因为,这种理解常常更符合开发人员的认知。从大多数开发人员的角度来看,“模型”这个概念,不管是否是面向“领域”的,常常区别于代码并且以图的形式存在(比如,典型的UML图)。实际上,这两种观点并不冲突。笔者在这里将前者Evans对领域模型的理解称为一种“广义的领域模型”,将后者特指图形式的领域模型称为一种“狭义的领域模型”。广义的与狭义的领域模型的关系如下图所示。
细心的读者将会发现,上图还传递了另外两个观点。
第一:图形式的领域模型通常对应于设计阶段,而代码形式的领域模型则通常对应于实现阶段。这种观点其实与张逸老师的《我对领域模型的理解》一文不谋而合。他认为,设计对领域模型的反映,就是设计模型;代码对领域模型的表达,就是实现模型。在设计阶段,软件设计人员需要基于对领域的理解建立对领域问题的解决方案;在实现阶段,开发人员则根据设计模型进行编码实现,使领域模型跃然于代码上。当然,在设计与实现之前还需要有分析阶段,领域专家与开发团队围绕领域梳理对业务知识的理解。不过,分析阶段关注的又是另外一些问题了,本文主要讨论设计与实现阶段及其软件制品之间的关系。
第二:狭义的领域模型与代码,同时作为广义的领域模型的特定表现形式,二者之间需要有明确的对应关系。也就是说,代码应该忠实地反映领域模型,(狭义的)领域模型也应该及时地表现出代码的变化。正是领域模型与代码实现之间的紧密联系才使模型变得有用,并确保我们所作出的深层次的设计能够最终转化为可运行的软件。当然,如果团队本身能力很强,完全只使用代码来表示领域模型,那就可以从根本上避免代码与领域模型不一致的问题。
言归正传,那么,所谓的领域模型和代码之间“明确的对应关系”具体指的是什么呢?笔者认为,这具体包含三个方面的对应关系:
术语方面,也就是说代码中使用的领域术语应与(狭义的)领域模型中的一致,这与Evans强调的在代码中贯彻使用通用语言(Ubiquitous Language)是一致的。
领域概念的分解,也就是说结构方面的设计,包括领域对象及其关系,在代码与领域模型中应是一致的。比如,UML类图与代码结构之间应该存在明确的对应。
领域概念之间的交互,也就是说行为方面的设计,包括领域对象之间的动作,在代码与领域模型中应是一致的。比如,UML时序图与代码行为之间应该存在明确的对应。
为了实现设计阶段的领域模型与实现阶段的代码之间的一致,从模型驱动工程(Model-Driven Engineering)的角度来看读者可以应用以下几种策略。
1、从模型到代码的转换
从模型到代码的转换(Model-to-Code Transformation)是模型驱动工程的一种典型技术,尤其活跃于2010年前后。将这种技术应用于领域驱动设计,可以帮助从领域模型自动化地生成代码,从而减少开发人员手动编写代码的工作量,一定程度上能够降低由于开发人员的疏忽造成的与领域模型的不一致。另一方面,由于其自动化的代码生成过程,该技术还被期望能够提高软件开发效率。
领域驱动设计社区推崇的「模型到代码转换策略的工具」包括SCULPTOR (http://sculptorgenerator.org/), LEMMA(https://github.com/SeelabFhdo/lemma)等。
然而,从模型到代码的转换方法存在两个固有问题。
其一,由于模型本身相比于代码总是不够具体的,因此从模型到代码的转换通常只能生成代码框架,其代码实现细节仍需由开发人员手动实现。这一过程仍然可能造成代码与领域模型的偏离。
其二,在开发人员基于生成的代码框架实现代码细节时,领域模型本身可能也在演化,那么,演化后的领域模型所生成的代码应该如何与开发人员正在修改的代码进行合并呢?二者之间的合并冲突问题又应该如何解决?
2、代码到模型的转换
从代码到模型的转换(Code-to-Model Transformation),是从模型到代码转换的逆向过程,可以被认为是模型驱动的逆向工程(Model-Driven Reverse Engineering)的典型技术。
将这种技术应用于领域驱动设计,可以帮助从代码中恢复出可视化的领域模型,所恢复的领域模型本身代表了“在代码中被实现的领域模型”,同时它也是对代码进行的一种抽象与可视化。利用恢复得到的图形式的领域模型,开发人员能够进一步对比设计的领域模型与实现的领域模型,从而快速发现和报告两者之间的分歧。
更进一步地,还可以在设计的领域模型与实现的领域模型之间进行反射建模(Reflexion Modeling),即通过可视化的映射模型表现出实现的领域模型相比于设计阶段的差异(包括新增、删除与修改元素),从而更直观地帮助开发人员识别代码是否偏离了领域模型。
此外,抽象与可视化后的代码视图还可以帮助开发人员进行知识消化(Knowledge Crunching),减少该过程对代码实现细节的依赖,从而降低该过程的认知复杂度。该策略的具体实施方法可以参考笔者最近在软件学报上发表的《一种面向领域驱动设计的逆向建模支持方法》一文(http://www.jos.org.cn/jos/article/abstract/6278?st=search)。
3、模型组合
模型组合(Model Composition)基于关注点分离的思想将软件系统划分为模型单元并通过合成处理技术来组装这些单元以解决系统的复杂性。
将这种技术应用于领域驱动设计,可以通过组合领域模型元素(及其代码)得到开发人员预期的领域模型与代码,从而降低开发人员手动实现代码的工作量,减小代码偏离领域模型。
这种策略,在笔者看来与目前大行其道的低代码类似,其本质上都通过组件化以及模型驱动工程等思想来减少开发人员的代码编写。目前,根据最新的学术文献综述(https://www.sciencedirect.com/science/article/pii/S0950584920300689),这种策略仍存在许多挑战,比如如何自动化地匹配要组合的模型元素,缺少工业规模的解决方案等。
4、模型与代码的可追溯性管理
模型驱动工程被广泛地用于多个软件制品之间的可追溯性管理,比如设计文档、源代码以及测试用例等。将这种技术应用于领域驱动设计,可以在领域模型和代码之间建立链接,并通过跟踪这些链接可视化地展示与维护领域模型和代码之间的一致性。
此外,如果辅以自动化的模型转换技术(比如从模型到代码转换),利用这种机制还可以自动化地维护领域模型和代码之间的链接。也就是说,当源模型(比如领域模型)被修改时,这种修改可以被自动化地传播到目标模型(比如代码)上。
缺乏适当的可追溯性信息的存储、处理与查询技术被学术界(https://www.sciencedirect.com/science/article/pii/S0950584912001346)认为是这种策略目前存在的局限性。笔者尚未看到这种策略在领域驱动设计社区的应用。
更重要的是,以上基于模型驱动工程的方法实际上都还依赖于某种领域特定语言(Domain Specific Language)。
比如,要想实现自动化的模型到代码的转换,期望的领域特定语言需要回答三个问题,即如何表示领域模型,如何表示代码,以及如何建立领域模型元素到代码元素之间的映射。
比如,如何在图形式的领域模型中表示聚合?如何在代码中表示聚合?如何在这两种形式的聚合之间建立关联?
既然如此,使用这些领域特定语言就很可能会对开发团队的建模语言与编程语言等做出一定限制,要想在工业界落地这些模型驱动工程方法仍然有很多问题值得被讨论。笔者在此列出的一二三不过是班门弄斧罢了。
本文作者是南京大学钟陈星博士,目前正从事领域驱动设计与微服务相关的学术研究。此研究对于领域驱动设计的工程实践而言,具有较高价值。作为国内目前唯一开展领域驱动设计方向学术研究的团队,钟博士在南京大学张贺教授的指导下,研读了大量领域驱动设计的书籍与论文,对这一领域具有颇深造诣。感谢钟博士的信任,邀请我加入该项目,使我能附骥参与此项研究,以贡献我在工程实践方面的一隅之见。