领域驱动设计-第一部分 运用领域模型
本书的两个前提:
- 在大多数软件项目中,主要的焦点应该是领域和领域逻辑
- 复杂的领域设计应该基于模型
什么是领域?每个软件程序都是为了执行用户的某项活动,或是满足用户的某种需求。这些用户软件的问题区域就是软件的领域。
例如,银行软件解决的是金融领域问题,滴滴解决的就是运输交通问题。
本书主要面向的是“敏捷开发过程",必须遵循两个开发实践:
-
迭代开发
-
开发人员与领域专家(PM)具有密切关系
领域的实质就是消化吸收大量知识,最后产生一个反应深层次领域知识并关键概念的模型。这是领域转件与开发人员的协作过程,领域专家精通领域知识,开发人员知道如何构建软件。由于开发是迭代的,因此,这个协作必须贯穿整个项目的生命周期。
模型在领域驱动设计中的作用:
-
模型和设计的核心相互影响
模型与实现紧密关联,我们可以基于模型的理解来解释代码
-
模型是团队所有成员使用的通用语言中枢
当开发人员和领域专家在将信息组织称为模型时,这一共同语言能够促使他们高效的协作
-
模型是浓缩的知识
模型记录了我们看待领域的方式。
第1章 消化知识
当开始编写软件时,其实我们知之甚少,高效率的团队需要有意识的积累知识,并持续学习。
知识消化并非一个孤立的活动,一般是在开发人员的领导下,由开发人员与领域专家来共同协作。
领域模型的不断精化迫使开发人员学习重要的业务原理,而不是机械的进行功能开发。领域专家被迫不断提炼自己已知的重要知识的过程往往也是完善其自身理解的过程,而且他们会渐渐的理解软件项目所必须的概念严谨性。开发人员则通过对知识去粗取精,将模型重塑为更有用的形式。
模型在不断改进的同时,也称为组织项目信息交流的工具。模型聚焦于需求分析。它与编程和设计紧密交互。
有效建模的要素:
- 模型和实现绑定
- 建立了一种基于模型的语言
- 开发一个蕴含丰富知识的模型
- 提炼模型
- 头脑风暴和实验
有用的模型很少停留在表面,随着对领域和应用程序需求的理解逐步加深,一些开始时不可能发现的巧妙抽象就会逐渐浮出水面,而它们恰恰切中问题的要害。
知识消化是一种探索,它永无止境。
示例:提取一个隐藏的概念
该程序用来预定一艘船再一次航程中要运载的货物
图:类图 预定代码如下:
public int makeBooking(Cargo cargo, Voyage voyage) { int confirmation = orderConfirmationSequence.next(); vayage.addCargo(cargo, confirmation); return confirmation; }
变更来了:由于订单取消,允许“超订”,如预定110%的载货量
代码变更:
public int makeBooking(Cargo cargo, Voyage voyage) { /*****************************************************/ double maxBooking = voyage.capacity() * 1.1; if((voyage.bookedCargoSize() + cargo.size()) > maxBooking) { return -1; } /*****************************************************/ int confirmation = orderConfirmationSequence.next(); vayage.addCargo(cargo, confirmation); return confirmation; }
虽然实现了预定110%的载货量,但是一个重要的业务规则被隐藏到了一个if条件判断中。我们应该考虑如何将这条规则更清晰的表达出来,让每个人都能看到它。并且随着规则的复杂,情况变得更加糟糕。
提取隐藏概念:超载规则是一个策略,我们可以使用STRATEGY模式来实现。
这样每个人都清楚超订是一个独特的策略,而且超订规则的实现即明确有独立。
第2章 交流与语言的使用
软件模型可以称为软件项目通用语言的核心。该模型是一组得自于项目人员中的概念,以及反映了领域深层含义的术语和关系。
UBIQUITOUS LANGUAGE(通用语言)
UBIQUITOUS LANGUAGE(通用语言)的词汇包括类和主要操作的名称。模型之间的关系成为所有语言都具有的组合规则。词和短语的语义反映了模型的语义。开发人员应该使用基于模型的语言来描述系统中的工作、任务和功能。这个模型应该为开发人员和领域专家提供一种用于互相交流的语言,而且领域专家还应该使用这种语言来讨论需求、开发计划和特性。语言使用得越普遍,理解进行的越顺畅。
持续使用UBIQUITOUS LANGUAGE(通用语言)可以暴露模型中存在的缺点,这样团队就可以替换不恰当的短语和组合。发现缺陷时,新的词汇会加入到语言中。这些语言上的改变也会在领域模型中引起相应的更改,并促使团队使用新的类图并重新命名代码中的类和方法。
将模型作为语言支柱。确保团队在内部的交流以及代码实现中坚持使用这种语言。在画图、写东西、讲话时也要使用这种语言。并通过尝试不同的表达方式来消除难点,然后重构代码,重新命名类、方法、模块,以便和模型保持一致。解决交谈中的术语混淆问题要意识到UBIQUITOUS LANGUAGE(通用语言)的改变就是对模型的改变。领域专家应该抵制不合适或无法充分表达领域理解的术语或结构,开发人员应该密切关注那些会妨碍设计的有歧义和不一致的地方。
场景1:最小化的领域抽象
用户:那么,当更改通关地点(customs clearance)地点时,需要重新定义整个路线计划喽
开发人员:使得。我们将从货运表(shipment table)中删除所有与该货物id相关联的行,然后将出发地、目的地、通关地点传递给Routing Service,他会重新填写货运表。cargo中必须设立一个布尔值,用于指示货运表中是否有数据
用户:删除行?行,就按你说的做。但是,如果先前根本没有指定通关地点,也需要这没做吗?
开发人员:是的,无论何时修改了出发地、目的地、通关地点,都将检查是否已经有货运数据,如果有,则删除它们,然后有Routing Service重新生成数据。
用户:当然,如果原有通关地点碰巧是正确的,我们就没有必要这么做了。
开发人员:没有问题。当让Routing Service每次重新加载或卸载数据会更容易些。
用户:是的,但为新航线指定所有支持计划的工作量很大,因此,除非非改不可,我们一般不想更该航线。
开发人员:哦,好的,当第一次输入通关地点时,我们需要查询表格,找到以前的通关地点,然后与新的通关地点进行比较,从而判断是否需要重做。
用户:这个处理不需要考虑出发地和目的地,因为航线在此总要变更。
开发人员:好的,我明白了。
场景2:用领域模型进行讨论
用户:那么,当修改通关地点时,需要重新指定整个路线喽
开发人员:是的。当更改Route Specification(路线说明)的任意属性时,都将删除原有的Itinerary(航线),并要求Routing Service基于新的Route Specification生成一个新的Itinerary。
用户:如果先前根本没有指定通关地点,也需要这么做吗
开发人员:是的,无论何时更改了Route Specification的任意属性,都将生成新的Itinerary。这也包括第一次输入某些属性
用户:当然,如果原有的通关地点碰巧是正确的,我们就不需要要这样做了
开发人员:哦,没问题。但让Routing Service每次生成一个新的Itinerary更容易些
用户:是的,但为新航线指定所有支持计划的工作量很大,因此,除非非改不可,我们一般不想更该航线。
开发人员:哦,那么需要在Route Specification添加一些功能。这样,当更改Route Specification中的属性时,查看Itinerary是否满足Route Specification。如果不满足,则需要由Routing Service重新生成Itinerary
用户:这个处理不需要考虑出发地和目的地,因为航线在此总要变更。
开发人员:好的,但每次只做比较就简单多了。只有当不满足Route Specification时,才重新生成Itinerary
讨论系统是要结合模型。使用模型元素及其交互来大声的描述场景,并且按照模型允许的方式将各种概念结合到一起。找出更简单的方式来讲出你要讲的话,然后将这些新的想法应用到图和代码中。
文档、图、设计文档
图可以帮助人们掌握某些类型的信息。UML图在传达对象之间的关系上游刃有余,并且擅长表现交互。简单非正式的UML图能够维系整个系统。但是UML图无法传达模型两个重要方面,一个方面模型所表示概念的意义,另一个就是对象应该做哪些事情。只通过图并不能完全的表达行为职责,需要补充文本或对话完成。
设计的重要细节应该在代码中体现出来,良好的实现应该是透明的,清楚地展示其背后的模型。
文档应该作为代码和口头交流的补充,并且要保持是最新的。
解释性模型提供了一定的自由度,可以专门为某个特定的主题定制一些表达能力更强的风格。解释性模型不必是对象模型,最好不是。这样可以避免人们错误认为这些模型与软件实现是一直的
示例 航运操作和路线
图:航运路线的类图
图:航运路线的解释性模型
第3章绑定模型和实现
如果项目建立的领域模型,那么这个模型就应能直接帮助开发可运行的软件。
模式驱动设计
严格的按照基础模型来编写代码,能够使代码更好的表达设计含义,并且使模型与实际的系统相契合。如果整个程序设计或者其核心部分没有与领域相对应,那么这个模型就是没有价值的,软件的正确性也是值得怀疑的。同时,模型和设计功能之间过于复杂的对应关系也是难于理解的,在实际项目中,当设计改变时也无法维护这种关系。若分析和设计之间产生严重分歧,那么在分析和设计活动中所获得的知识就无法彼此共享。
**MODEL-DRIVEN DESIGN(模型驱动设计)**不再将分析模型和程序设计分开,而是寻求一种能够满足这两方面需求的单一模型。
软件系统的各个部分的设计应该忠实的反映领域模型,以便体现出两者之间的明确的对应关系。我们应该反复检查并修改模型,以便软件可以更加自然的反映模型,即使想让领域反映出更深层次的领域概念时也应如此。我们需要的模型不但应该满足这两个需求,还应该能够支持健壮的通用语言。
从模型中获取用于程序设计和基本职责分配的术语。让程序代码成为模型的表达,代码的改变可能会是模型的改变。
建模范式和工具支持
要保证模型和设计的一致性,需要运用一些范式,还需要支持建模范式的软件开发工具和语言,比如面向对象编程。
什么是范式?
范式从本质上讲是一种理论体系、理论框架。在该体系框架之内的该范式的理论、法则、定律都被人们普遍接受。
建模范式是逻辑的,而模型则是一组逻辑规则以及这些规则所操作的事实。
模式:HANDS-ON MODELER(亲身实践的建模者)
模型没有和设计保持一致的原因:
- 模型的一些意图在传递过程中丢失了
- 由于模型和实现以及技术等因素相互影响,建模者无法获得反馈
任何参与建模的技术人员,不管在项目中的主要职责是什么,都必须花时间了解代码。任何负责修改代码的人员都必须学会使用代码来表达模型。