前文回顾
上一篇介绍了该书的整体结构,它所包含的4个部分。另外,我们还学习了一些领域模型相关的基本概念,比如什么是模型,建模的重要性,以及负责创建领域模型的角色。
这一篇我们继续学习该书的第一部分,运用领域模型。
使用哪种语言建模
为了更好的承载这些积累的知识,作者Eric提出了通用语言模式(UBIQUITOUS LANGUAGE):
领域模型可成为软件项目通用语言的核心。该模型是一组得自于项目人员头脑中的概念,以及反映了领域深层含义的术语和关系。这些术语和相互关系提供了模型语言的语义,虽然语言是为领域量身定制的,但就技术开发而言,其依然足够精确。正是这条至关重要的纽带,将模型与开发活动结合在一起,并使模型与代码紧密绑定。
通用语言指的不是中文、英语、日语这些自然语言,而是描述领域模型的词汇,包括类和主要操作的名称。如果不使用通用语音,领域专家和开发人员使用各自的术语,那么团队成员之间的沟通就需要翻译。翻译工作导致各类促进深入理解模型的知识和想法无法结合到一起。这种间接沟通的后果是什么呢?
由于软件的各个部分不能够浑然一体,因此这就导致无法开发出可靠的软件。
为了让模型成为领域专家和开发人员之间互相交流的语言,我们需要在各个开发活动中使用这种语音。开发人员使用这种语言来描述系统中的组件和功能。领域专家使用这种语言来描述需求,开发计划和特性。
将模型作为语言的支柱。确保团队在内部的所有交流中以及代码中坚持使用这种语言。在画图、写东西,特别是讲话时也要使用这种语言。
Eric还给出了一个小技巧,就是要大声地讨论模型。讨论时要使用模型元素来大声描述场景,并且按照模型允许的方式将各种概念结合到一起。找到更简单的表达方式来讲出你要讲的话,然后将这些新的表达落实到图和代码中。虽然暂时还没有给出详细的建模步骤,但是我们知道了需要用通用语言,在互相交流学习中建模。
使用通用语言的样例
书中给出了两个场景作为比较。
场景1:最小化的领域抽象
用户:那么,当更改清关(customs clearance)地点时,需要重新制定整个路线计划啰。
开发人员:是的。我们将从货运表(shipment table)中删除所有与该货物id相关联的行,然后将出发地、目的地和新的清关地点传递给RoutingService,它会重新填充货运表。Cargo中必须设立一个布尔值,用于指示货运表中是否有数据。
用户:删除行?好,就按你说的做。但是,如果先前根本没有指定清关地点,也需要这么做吗?
开发人员:是的,无论何时更改了出发地、目的地或清关地点(或是第一次输入),都将检查是否已经有货运数据,如果有,则删除它们,然后由RoutingService重新生成数据。
用户:当然,如果原有的清关数据碰巧是正确的,我们就不需要这样做了。
开发人员:哦,没问题。但让RoutingService每次重新加载或卸载数据会更容易些。
用户:是的,但为新航线制定所有支持计划的工作量很大,因此,除非非改不可,我们一般不想更改航线。
开发人员:哦,好的,当第一次输入清关地点时,我们需要查询表格,找到以前的清关地点,然后与新的清关地点进行比较,从而判断是否需要重做。
用户:这个处理不必考虑出发地和目的地,因为航线在此总要变更。
开发人员:好的,我明白了。
场景2:用领域模型进行讨论
用户:那么,当更改清关地点时,需要重新制定整个路线计划啰。
开发人员:是的。当更改RouteSpecification(路线说明)的任意属性时,都将删除原有的Itinerary(航线),并要求RoutingService(路线服务)基于新的RouteSpecification生成一个新的Itinerary。
用户:如果先前根本没有指定清关地点,也需要这么做吗?
开发人员:是的,无论何时更改了RouteSpec的任何属性,都将重新生成Itinerary。这也包括第一次输入某些属性。
用户:当然,如果原有的清关数据碰巧是正确的,我们就不需要这样做了
开发人员:哦,没问题。但让RoutingService每次重新生成一个Itinerary会更容易些。
用户:是的,但为新航线制定所有支持计划的工作量很大,因此,除非非改不可,我们一般不想更改路线。
开发人员:哦。那么需要在RouteSpecification添加一些功能。这样,当更改RouteSpecification中的属性时,查看Itinerary是否仍满足Specification。如果不满足,则需要由RoutingService重新生成Itinerary。
用户:这一点不必考虑出发地和目的地,因为Itinerary在此总是要变更的。
开发人员:好的,但每次只做比较就简单多了。只有当不满足RouteSpecification时,才重新生成Itinerary。
不难看出,场景2中使用了基于领域模型的术语RouteSpecification(路线说明)和Itinerary(航线),是得描述更为精准。
模型和文档有什么关系
模型应当以什么样形式表现出来呢?很多人会想到UML统一建模语言。UML可以用图形很好地展现对象之间的关系,帮助我们讨论领域模型。但是Eric提醒我们:
当人们必须通过UML图表示整个模型或设计时,麻烦也随之而来。很多对象模型图在某些方面过于细致,同时在某些方面又有很多遗漏。
UML图无法传达模型的两个最重要的方面,一个方面是模型所表示的概念的意义,另一方面是对象应该做哪些事情。
因此我们需要使用其他自然语言作为补充,来表达模型的确切含义。UML只是一种沟通和解释的手段,它并不是模型本身。不能强制用图来表示全部模型或设计,因为这样会削弱图的清晰表达的能力。所以模型可以表现为UML加自然语言构成的文档。
但是在敏捷开发大行其道的今天,文档的处境很尴尬。很多极限编程的拥护者甚至提倡没有文档,代码即文档。其实仔细看看敏捷宣言,原话是工作的软件高于详尽的文档,并没有完全否定文档的意义。那么文档究竟应该处于一个什么样的位置呢?Eric告诉我们,文档应作为代码和口头交流的补充。
文档不应再重复表示代码已经明确表达出的内容。代码已经含有各个细节,它本身就是一种精确的程序行为说明。其他文档应该着重说明含义,以便使人们能够深入理解大尺度结构,并将注意力集中在核心元素上。当编程语言无法直接明了地实现概念时,文档可以澄清设计意图。
设计文档的最大价值在于解释模型的概念,帮助在代码的细节中指引方向。
很多人倡导代码即文档的主要原因是:代码不会骗人,而文档会。很多时候文档没有随着代码的演变而得到更新,如果使用了这种过期的文档,还可能会对项目带来伤害。所以文档应当鲜活并保持最新。但是如果要持续更新所有的文档,开发人员有没有意愿先不说,还会带来不少工作量。如何去平衡这个问题?Eric建议最大限度地减少文档,确保文档只是作为代码和口头交流的补充,可以避免文档与项目脱节。
模型和代码有什么关系
Martin Fowler在序言中提到:
在领域建模过程中不应将概念与实现割裂开来。高效的领域建模人员不仅应该能够在白板上与会计师进行讨论,而且还应该能与程序员一道编写Java代码。
模型与代码实现应该是紧密相连的,创建模型的人和编写代码的人也不应该割裂开。然而领域驱动设计以外的很多设计方法却提倡使用分析模型,一种与程序设计毫不相干,只作为对业务领域的分析结果的模型。通常由分析员创建,作为需求传递给开发人员。分析模型和最终的程序设计只能保持了松散的对应关系。由于前期创建分析模型是没有考虑程序设计,一旦编码开始,细节问题就会层出不穷,慢慢地纯粹的分析模型就会被抛到一边。这种开发模式至今仍然司空见惯。Eric指出:
如果整个程序设计或者其核心部分没有与领域模型相对应,那么这个模型就是没有价值的,软件的正确性也值得怀疑。同时,模型和设计功能之间过于复杂的对应关系也是难于理解的,在实际项目中,当设计改变时也无法维护这种关系。若分析与和设计之间产生严重分歧,那么在分析和设计活动中所获得的知识就无法彼此共享。
如何来解决这个问题呢?Eric适时提出了模型驱动设计模式(MODEL DRIVEN DESIGN),试图寻找一种单一模型,即能满足分析模型的需求又能满足程序设计的需求。事实证明绑定模型和程序设计是切实可行的,但是需要注意:
这种绑定不能够因为技术考虑而削弱分析的功能,我们也不能接受那些只反映了领域概念却舍弃了软件设计原则的拙劣设计。
这是模型驱动设计模式(MODEL DRIVEN DESIGN)的两个基本要素,即模型要支持有效的实现,并抽象出关键的领域知识。
在开发语言方面,Java这类面向对象的语言天然适合用来表达模型。而且只有用代码表现模型概念时,面向对象设计的突破之处才能真正彰显出来。面向对象设计是目前大多数项目所使用的建模范式,也是此书中使用的主要方法。
书中提到一个曾经困扰了作者Eric多年的问题,负责创建模型的分析员和负责程序设计的开发人员是不是应该区分开?他曾经在一个项目中负责开发领域模型,用于指导程序设计,然而项目管理层却禁止他编写代码或者与程序员讨论细节问题,他们认为负责分析的人就专心分析建模,编写代码是在浪费时间。Eric分析领域知识,提炼出了一个不错的领域模型,但是最终却没派上用场。因为模型的部分意图在传递过程中丢失,而且代码实现对模型的影响得不到反馈。于是Eric提出了亲身实践的建模人员的模式(HANDS-ON MODELER),这个模式也被翻译成建模人员参与程序开发。
如果编写代码的人员认为自己没必要对模型负责,或者不知道如何让模型为应用程序服务,那么这个模型就和程序没有任何关联。如果开发人员没有意识到改变代码就意味着改变模型,那么他们对程序的重构不但不会增强模型的作用,反而还会削弱它的效果。
到这里第一部分就读完了。
系列文章
- [DDD读书笔记] 运用模型①什么是领域模型
- [DDD读书笔记] 运用模型②通用语言
- [DDD读书笔记] 构造块①分离领域层
- [DDD读书笔记] 构造块②实体、值对象和服务
- [DDD读书笔记] 构造块③模块
- [DDD读书笔记] 构造块④聚合与工厂
- [DDD读书笔记] 构造块⑤仓库
- [DDD读书笔记] 构造块⑥实战模拟
- [DDD读书笔记] 重构①突破
- [DDD读书笔记] 重构②SPECIFICATION模式
- [DDD读书笔记] 重构③柔性设计
- [DDD读书笔记] 重构④使用分析模式和设计模式建模
- [DDD读书笔记] 战略设计①模型上下文策略
- [DDD读书笔记] 战略设计②精炼
- [DDD读书笔记] 战略设计③大型结构