域模型在交流中扮演的角色

简介: 域模型在交流中扮演的角色

问题:我对于领域模型如何表示始终还不太明白。按照Evans书里的说法,代码应当是领域模型的主要部分,文档、图表作为补充。另外一方面,领域模型应当是所有参与者都能够理解的,而我觉得用户不太可能去理解代码。


比如以Evans书里举的,可以超载10%这一点,书里是通过一个Strategy模式来表达这个知识,从程序员的角度看很清晰了,但是从用户的角度看,还是不太能够明白吧。


请教张老师如何看待这个问题?


回答:其实从模型的角度看,有几个层次,Eric说的是模型驱动模型。重点是模型。这几个层次包括:

  • 现实模型即问题域
  • 领域模型
  • 设计模型
  • 代码模型


实际上领域模型是搭建现实模型需求问题到解决方案的桥梁。领域模型是领域概念尤其是统一语言的可视化表现,在Eric写作《领域驱动设计》一书的时代,领域模型多数以UML来表达。


这里要注意一个历史问题。在Eric写作该书的时代,正是UML与逆向工程大行其道的时代。当时有很多人都在倡导运用建模工具如Rational Rose来建模,进而利用图形化的模型生成代码。这个思想在当时人们的心中会是未来编程的一个主流发展方向,也有很多人在朝着这个方向努力,随之也催生了诸多建模工具的诞生,UML得到大量的普及,甚至差点成为了软件设计的唯一标准。这也是Eric倡导模型驱动设计的一个历史背景,至少我认为他在写书时是收到这个思想影响的。最终,这种设计思想并没有得以实现,人们低估了编程的复杂度,高估了模型的重要性。所以,Eric的书是有历史局限性的。尽信书不如无书,这是阅读他的书要注意的。书中讲的一些实践,未必都对。


但是,Eric的领域驱动设计是一个方法学,是开放的,也是逐步演进的。事实上,已经有很多人站在Eric的肩膀上,提出了很多切合实际,也吻合软件行业发展趋势的实践与模式,作为领域驱动设计的补充。例如领域事件、六边形架构以及CQRS等。Eric自己也认可这种演进。


回到模型上来。我认为领域模型就是对领域概念的抽象,你说的超载10%其实就是业务规则,所以可以抽象为一个领域概念,在与领域专家进行交流时,可以通过领域模型的这个领域概念来表达,而不是直接使用代码。


设计模型则是对领域模型的一种技术呈现,乃至于是从技术角度的一种精化与演进,例如通过引入设计原则与模式,可以实现领域模型对象更好的职责分配,通过抽象实现解耦,定义更加合理的封装。这时,设计模型要取决于你的编程范式,如采用面向对象还是函数式编程。同样以超载规则为例,面向对象范式的设计模型就是抽象的服务接口,函数式就是一个函数。如果规则需要组合,前者就利用继承或委派,后者就用组合子。


代码模型是设计模型的具体实现,它是遵循设计模型来实现的,采用不同的语言和框架,也会有区别。例如,有的语言可以非常方便地定义值对象,如Scala的Case Class,就是值对象的语法糖。


整体来看,领域模型是团队与领域专家交流所用,设计模型是团队的设计人员交流的工具,代码模型自然为程序员服务。这三个模型之间的关系如下图所示:


image.png


随着时间的推移,这三种模型可能会出现不同步的问题。Eric在书中讲解模型驱动设计时也提到了这个问题。如上图所示,领域模型为指导设计模型,设计模型是领域模型的实现,而随着设计模型的演进,我们又需要这种变更体现在领域模型中,保证模型是领域的真实表达。至于代码模型,一方面是遵循设计模型进行代码的实现,同时还应该尽力保障代码模型要表达领域概念,这不仅仅是从代码可读性的角度来考虑,也牵涉到代码对领域逻辑的呈现。这也是为什么在DDD的编程实践中,我们为什么希望避免贫血模型,希望避免使用无法表达领域行为的get和set方法的原因。


倘若要在代码模型中体现领域模型,一种更好的做法是使用DSL,即领域特定语言。但DSL的实现其实是一个相对漫长的积累过程,不同语言的领域表达能力也不相同。所以DSL主要还是用在一些相对复杂但又相对稳定专业的行业中,例如通信和金融行业,就有DSL的开发需求。当然,即使不去做一套DSL,我们也可以借鉴DSL的思想,例如通过Fluent Interface之类的实践改进代码的表达能力。


还有一种做法就是利用BDD编写验收测试,形成活文档(Live Document)。BDD框架如Cucumber、Robot Framework、RSpec其实就是一种DSL,通过这些框架可以编写符合自然语言规范的测试用例,形成一个中规格(Specification),这些测试用例又是能够运行的代码,这就相当于搭建了代码与需求规格的桥梁。不过,这种活文档只能应用在测试保障上,它可以帮助我们建立一种更好的交流机制,但并不能取代设计模型和代码模型。

相关文章
|
SQL 算法 前端开发
【MybatisPlus】MP解决四种表与实体的映射问题,以及id自增策略
MP解决四种表与实体的映射问题,以及id自增策略
3627 0
【MybatisPlus】MP解决四种表与实体的映射问题,以及id自增策略
|
JavaScript 内存技术
node与npm版本对应关系以及使用nvm管理node版本
node与npm版本对应关系以及使用nvm管理node版本
7106 0
|
9月前
|
SQL JSON 前端开发
若依RuoYi脚手架二次开发教程(二次开发必学技能)
本次我们将通过一个菜品管理模块开发的案例,来演示拿到若依框架后,如何在若依管理系统上进行二次开发,升级改造为自己的管理系统。适合以若依作为项目脚手架的公司开发人员、毕业设计的学生及开源项目学习者。
5302 1
若依RuoYi脚手架二次开发教程(二次开发必学技能)
|
安全 Java 网络虚拟化
隐藏 IP 地址调用外部接口:探索与实践
隐藏 IP 地址调用外部接口:探索与实践
300 0
|
监控 Go 开发者
掌握Go语言中的日志管理
【8月更文挑战第31天】
156 0
|
11月前
|
开发工具 git 开发者
关于git 解决分支冲突问题(具体操作,包含截图,教你一步一步解决冲突问题)
本文通过具体操作和截图,详细讲解了如何在Git中解决分支冲突问题,包括如何识别冲突、手动解决冲突代码、提交合并后的代码,以及推送到远程分支。
2648 3
关于git 解决分支冲突问题(具体操作,包含截图,教你一步一步解决冲突问题)
|
缓存 网络协议 网络虚拟化
网络技术基础(15)——DHCP简介与配置
【3月更文挑战第3天】刚加完班又去南京出差了,实在是太忙了。。。。网络基础笔记(加班了几天,中途耽搁了,预计推迟6天),这篇借鉴了之前师兄的笔记。
|
10月前
|
缓存 负载均衡 NoSQL
基于木舟平台浅谈surging 的热点KEY的解决方法
【11月更文挑战第13天】本文介绍了木舟平台及Surging框架中热点KEY的概念与解决方案。热点KEY指在缓存或分布式系统中频繁访问的数据键,如电商中的热门商品ID。为避免缓存击穿等问题,文章提出了设置热点数据永不过期、多级缓存架构、缓存预热、限流和降级策略以及分布式系统层面的优化等方法。
136 4
|
10月前
|
XML 前端开发 Android开发
Android:UI:Drawable:View/ImageView与Drawable
通过本文的介绍,我们详细探讨了Android中Drawable、View和ImageView的使用方法及其相互关系。Drawable作为图像和图形的抽象表示,提供了丰富的子类和自定义能力,使得开发者能够灵活地实现各种UI效果。View和ImageView则通过使用Drawable实现了各种图像和图形的显示需求。希望本文能为您在Android开发中使用Drawable提供有价值的参考和指导。
231 2
|
设计模式 安全 Go