领域驱动设计笔记 by DN文档格式.docx
- 文档编号:20602741
- 上传时间:2023-01-24
- 格式:DOCX
- 页数:17
- 大小:32.94KB
领域驱动设计笔记 by DN文档格式.docx
《领域驱动设计笔记 by DN文档格式.docx》由会员分享,可在线阅读,更多相关《领域驱动设计笔记 by DN文档格式.docx(17页珍藏版)》请在冰豆网上搜索。
6.2如何显示地建模这些隐含的概念?
《领域驱动设计》读书笔记(7)——柔性设计15
释意接口15
无副作用函数15
断言15
概念轮廓16
孤立类16
操作封闭16
该死的贫血模型
现在,一说到领域驱动设计(DDD),人们就会想到贫血模型及与其相对的充血模型,就好像这两者都是正确的实践,只是两种不同的风格而已。
但是,事实上领域驱动设计根本就不存在贫血和充血之分,贫血的根本就不是领域驱动设计,领域驱动设计的模型本来就是有丰富的行为的!
所谓的贫血模型,其实应该是在实践领域驱动设计时应该避免的反模式,应当被尽量的避免。
早在2003年,MartinFolwer就已经谈论过这个问题,这篇文章在:
我也写过了多年的贫血模型的代码,但这两年来,我越来越意识到它的危害性!
贫血模型让我们把业务逻辑和实体数据分离,把大量的逻辑写在所谓的Service类里面,结果就导致实体成了C语言的结构体,而service就成了一个个C函数,又回到了过程化地编写C程序的时代。
MartinFowler把这种service叫做transactionscript,就是事务脚本,换句话说,实体变成了数据库的一个变体,service变成了针对于这个变体进行编程的脚本,本质上就是一段事务脚本,没有领域概念体现的地方。
面向数据库编程在应对复杂多变的领域问题时的不足,早已经被人们认识到,所以人们才会发明出用领域驱动设计这种方法。
然而贫血模型让我们在领域驱动设计前面来了一个180度大转弯,越走越远。
领域驱动设计借助面向对象来实现领域,可以说领域驱动设计正是面向对象应用于富业务逻辑软件开发上的一种更具体的解决方案。
贫血模型,不仅不是领域驱动设计,还鼓励人们背离面向对象,回到过程化的编程方式。
戴上贫血模型的有色眼镜,所有的东西,不是结构体一样的实体,就是直接访问数据的DAO,剩下的都是Service,而service则成以一个过程的集合。
本来强大的面向对象的能力,在这样的编程模式中被遗忘了。
把数据和行为分离,就直接不符合封装的原则。
把所有逻辑都放到几个service类中,而让其它的类都没有逻辑,这也不符合职责单一的原则。
贫血模型并不符合面向对象的思想。
为什么我们会写出贫血模型这样的代码呢?
我想,这和spring+hibernate等误导有关系。
在hibernate和JavaEE中,被冠以entity名称的数据库表映射类,实际上并不一定是领域模型驱动里的实体的概念。
但是我们却被误导了,以为映射类就是实体类。
然而,映射类是由hibernate动态创建的,想在其中通过spring注入其它业务对象,实现一些复杂的功能,不容易实现。
为了避免麻烦,于是大家习惯于让映射类里只包含和数据库对应的数据,而不包含业务逻辑。
进尔把所有的业务逻辑都搬到一个个Service里实现,逐渐形成了贫血模型的编程习惯。
要想领域驱动开发,首先就不要误入所谓贫血模型的邪道!
《领域驱动设计》读书笔记
(1)——让模型发挥作用
《领域驱动设计》读书笔记第一部分的读书笔记,主要讲领域驱动设计的基础理论,和基本的团队实践原则。
第一部分让模型发挥作用
引子
1DDD中模型的作用
1.模型与设计核心的相互塑型
模型驱动了设计,最终产生和模型绑定的代码;
用代码实现模型时又发现问题,反作用于模型。
2.模型是所有团队成员所使用的语言的核心
团队成员在交流过程中用统一的语言围绕模型进行交流。
由于模型和代码之间的绑定关系,讨论模型就是在讨论程序;
又由于模型是对于业务分析而来的,所以讨论模型也就是在讨论业务。
实现开发人员和业务人员之间的无障碍沟通。
3.模型用来提炼知识。
不断地改进模型,可以加深我们对领域的认识,发现领域深层次的问题,这些将成为我们在该领域的知识,这些知识被模型承载,并通过学习模型传承下去。
2软件的核心
软件的核心是它为用户解决领域相关问题的能力。
其他的一些特征,尽管它们也许是必需的,但也是用来支持这个核心目的的。
但是很可惜,开发人员往往不懂领域,而领域专家也不懂程序。
领域驱动设计方法,就是要找到一个中间的桥梁,让开发人员和领域专家可以沟通,让程序和领域相绑定。
这个桥梁就是领域模型。
第1章消化知识
1.1有效建模的因素
领域模型源自于开发人员和领域专家的交流和共同学习的过程,有效的领域模型具备以下一些因素:
1.模型与实现相互绑定。
不能应用于实现在模型,是没有意义的。
2.基于模型生成一种语言(用于交流的语言,不是指形式化的计算机语言)。
如果不能生成一种语言来描述模型,沟通交流将难于持续下去。
3.开发了一个包含丰富知识的模型。
模型是包含了丰富的状态和行为了,这些状态和行为体现了这个领域的知识。
4.提炼模型。
模型需要不断的改进,因为很难一次性成功的找到领域的模型,而且领域自身也在不断的变化。
1.2知识消化
1.3持续学习
1.4知识丰富的设计
1.5深层模型
第2章交流及语言的使用
面对面的交流是最有效了,在交流的过程中设计模型,形成团队自己的描述模型的语言,让这个语言没有歧义。
2.1通用语言
当一个项目的语言存在断层时,会面临一系列的问题。
领域专家使用自己的行话,而技术团队成员却按照设计思路调整和使用自己的语言去讨论领域。
天天进行讨论时所使用的术语与嵌入到代码(一个软件项目最重要的最终产品)中的术语是分裂开来的。
甚至可能是同一个人,在编写文档或者代码时,也会使用跟交流讨论时完全不同的语言,这会导致对于领域的某些深入描述只是短时间内存在,却无法反映到代码或者文档编写中。
语言转换减弱了交流的效果,使得知识积累也不尽如人意。
然而任何一种方言都不能够成为通用语言,因为它们都无法满足所有需求。
要将模型作为语言的骨干。
团队在所有的交流与代码中都应该练习使用这种语言。
在图、文档编写,尤其是发表意见过程中都使用相同的语言。
通过用替代表达方法进行实验来消除实际中遇到的困难,它们反映了替代模型的含义。
然后按照新的模型重新进行编码,并对类、方法和模块进行重命名。
在交谈中辨明术语中含糊的词义,就像我们对普通的词语意义达成一致意见那样。
要意识到通用语言中的变化也是模型中的变代。
领域专家应该排除掉传达领域意思时不易使用或不适当的术语或结构;
开发人员应该注意防止可能导致设计失败的模糊性或不一致性。
2.2利用对话改进模型
结合模型来讨论系统。
使用模型的元素和元素之间的交互来大声描述场景,按照模型允许的方式把概念组合在一起。
找到更简单的方式来说出要表达的内容,然后将这些意见应用到图形和代码中。
2.3一个团队,一种语言
2.4文档和图
文档和图用来描述在代码难中以被描述的高层次的模型的意义和目的,而代码则是对模型最细节的描述。
代码能够描述得清楚的事情,就不需要文档和图来描述。
2.5说明性模型
第3章将模型和实现绑定
3.1模型驱动设计
如果一个设计,或者它的核心部分,不能够映射到领域模型上,那这个模型是没有价值的,软件的正确性也是值得怀疑的。
同时,模型与设计功能之间复杂的映射常常难于理解,并且在实践中,当设计发生变化的时也不可能维护。
在分析与设计之间存在致命的隔阂,从任何(分析和设计)活动中获得的知识都无法提供给另一方。
应该设计出能够非常精确地反映领域模型的部分软件系统,这样映射就会明显。
如同您尽力让模型更深层次地反映领域一样,再次研究模型并修改,使得它能够让软件更加自然地实现。
需要这样的单个模型,除了要支持一种健壮的通用语言之外,还要很好地完成这两个目的。
从模型中得到的术语可以用于设计和职责的基本分配。
代码成为模型的表达形式,因此对代码的改变可能也变成对模型的一种改变。
它的影响将相应波及到项目中的其他活动。
3.2建模范型和工具支持
过程化的程序语言,如C,往往缺少足够的抽象能力来让程序和领域直接对应,而是迫使程序员思考让计算机做一系列的什么样的操作过程来完成任务。
要将实现与模型严格地绑定在一起,通常需要支持建模范型的软件开发工具和语言,例如面向对象的编程。
3.3突出主旨:
为什么模型对用户很关键
让用户理解模型,用户能更好的使用程序。
3.4实践型建模人员
如果编写代码的人认为他们不对模型负责,或者并不理解如何让模型在一个应用程序中发挥作用,那么模型对于软件来说根本就没有用。
如果开发人员没有意识到改变代码会同时改变模型,那么他们的重构工作只会减弱模型的作用而不是增强。
其间,当一个建模人员与实现过程相分离的时候,他/她永远不会学会(或者说是错过了)对实现上的一些约束的感觉。
模型驱动设计的基本约束——模型要支持高效的实现和模型要抽象关键的领域知识——已经丢失了一半,最终的模型变得不再实用。
最后,如果分工阻碍了协作,在对模型驱动设计进行编码时,不能传递细节,那么经验丰富的设计人员的知识和技术不能够传递给其他的开发人员。
负责建模的技术人员必须花时间接触代码,而不论他或她是否在项目中担当主要角色。
任何负责代码修改的人员都要学习通过代码表达模型。
每个开发人员都必须参与一些级别的模型讨论中,并与领域专家接触。
负责不同工作的人员都必须自觉地和接触代码的人交流,通过通用语言动态交换模型想法。
《领域驱动设计》读书笔记
(2)——分离领域
主要是第四章,讨论如何通过分层来实现领域代码和其它代码区分开来。
第二部分模型驱动设计的构建块
为了让软件与模型始终保持一致,不管实际处理有多么麻烦,都必须运用建模和设计的最佳实践。
领域驱动设计的设计风格大部分遵循职责驱动设计原则,尤其是其中的契约式设计的观点,而这些原则和观点都来自于面向对象编程的最佳实践。
领域驱动设计利用分层体系结构来把领域代码从其它代码中分离出来,利用实体,值对象,服务,工厂,聚合,仓库等具有不同职责的几类对象来对领域建模。
第4章分离领域
和领域模型绑定的代码是最有价值的代码,然而它往往只占有整个项目代码的一小部分,只有把这么小部分的核心代码分离出来,才有可能实现持续地把握模型。
4.1分层架构
在面向对象的程序中,用户界面(UI)、数据库和其他支持代码,经常被直接写到业务对象中去。
在UI和数据库脚本的行为中嵌入额外的业务逻辑。
出现这种情况是因为从短期的观点看,它是使系统运行起来的最容易的方式。
当与领域相关的代码和大量的其他代码混在一起的时,就很难阅读并理解了。
对UI的简单改动就会改变业务逻辑。
改变业务规则可能需要小心翼翼地跟踪UI代码、数据库代码或者其他的程序元素。
实现一致的模型驱动对象变得不切实际,而且自动化测试也难以使用。
如果在程序的每个行为中包括了所有的技术和逻辑,那么它必须很简单,否则会难以理解。
推荐的分层结构是:
用户界面层->
应用层->
领域层->
基础结构层
将一个复杂的程序进行层次划分。
为每一层进行设计,每层都是内聚的而且只依赖于它的下层。
采用标准的架构模式来完成与上层的松散关联。
将所有与领域相关的代码都集中到一层,并且将它与用户界面、应用层和基础结构层的代码分离。
领域对象可以将任务的重点放在表达领域模型上,不需要关心它们自己的显示、存储和管理应用任务等内容。
这样使模型发展得足够丰富和清晰,足以抓住本质的业务知识并实现它。
4.1.1层间的联系
上层对下层的通信是简单的,但是要实现下层对上层的通信需要使用一些技巧或者架构模式。
为了实现复杂的领域层和应用层及用户界面层的通信,可以采用回调,观察者模式,或者应用MVC架构模式。
为了基础结构层提供的功能,有时也需要领域层继承基础结构层的基类。
总之,只要它们坚持隔离领域层的原则,这些方法都是可以的。
4.1.2架构框架
为了简化开发,项目中往往采用一些框架(j2ee,spring,hibernate…),如果让领域模型和框架结合得太紧,往往会受到框架的束缚导致建模的困难,最好在框架之上用普通的Java对象来实现大部分的业务逻辑,保证领域对框架的较低的依赖程度。
模型属于领域层
当领域逻辑和程序的其它关注点混合在一起时,要达到一致是很不现实的。
隔离领域实现是领域驱动设计的前提。
把领域逻辑嵌入到UI中的所谓的智能UI设计模式,是领域驱动设计的反模式。
《领域驱动设计》读书笔记(3)——软件中的模型描述
第5章主要讲领域模型驱动开发中,用软件来描述模型时用到的要素,有实体,值对象,服务等。
这些要素是在对领域模型中各种对象的抽象,这样抽象的方法也是源自于作者的实践,能够掌握这些元素,对于掌握领域建模非常重要。
第5章软件中的模型描述
用面向对象来为领域建模,又可以为把这些对象分成若干类,进而抽象出了领域驱动设计中的模型要素。
5.1关联
对象之间的双向关联虽然会让某些操作变得简便,便是会使代码变得难以理解和维护,因此应该简化对象之间的关联关系:
∙指定一个导航方向
∙通过加入限定符来有效地减少关联的多重性
∙清除不必要的关联
坚持对关联进行严格的约束,如实反映领域中的使用偏向,不仅仅会让这些关联更易于交流、简化其实现难度,还能突出剩下的双向关联的重要性。
当一个双向关系是领域中的一个语义特性时,当它是应用系统的功能必须实现的需求时,我们就要保留双向的导航来体现它。
显而易见,最极端的简化方式是:
如果关联不是手头任务的本质,或者不能反映出模型对象的基本含义,那么它就应该被完全取消。
5.2实体(又称引用对象)
有些对象并不主要是由它们的属性来定义的。
它们体现了标识在时间上的延续性,经常要经历不同的形态。
有时,一个对象与另一个对象有着不同的属性,但它们是相互匹配的;
有时,一个对象与另一个对象有着相同的属性,但是它必须能象跟那些对象区分开来。
弄错对象标识会导致数据破坏。
以标识作为其基本定义的对象称为实体。
如果一个对象是通过标识而不是属性来确定的,那么就在模型中把标识作为这个对象定义的基本要素。
保持类的定义简单明了,并着重考虑其生命周期的连续性和惟一性。
定义对象的方法要能够把每个不同的对象区分开来,而无需去考虑它的形态和历史。
对那些需要匹配通过属性比较来匹配对象的情况保持警惕。
确保生成标识的操作能够为每个对象生成一个唯一的结果,这可以通过在标识中附加一个具有惟一性保证的符号来实现。
生成的标识可能来自外界,也可能是由使用这个标识的系统随机产生的;
不管是用什么方法,标识必须满足其在模型中所具有的惟一性。
模型必须对“怎么才是同一个事物”的具体含义作出定义。
实体(entity)这个词在JavaEE开发中被滥用了,更多地表示的是映射到了数据库表的类,一定要把这两个概念区分开。
同样一个事物,在不同的问题域里面,不同的领域里面,可能是实体,也可能不是实体,一切要看在它的领域里面,人员最关心的是它的属性,还是它的标识。
5.3值对象
如果一个对象代表了领域的某种描述性特征,并且没有概念性的标识,我们就称之为值对象。
值对象就是那些在设计中我们只关心它们是什么,而不关心它们是谁的对象。
如果我们只关心模型中一个元素的属性,那么就把这个对象划分成值对象。
用它来描述它所要表达的那些属性的意义,并提供相应的功能。
把值对象看成是不可变的。
不要给它任何标识,这样可以避免实体的维护工作,降低设计的复杂性。
值对象会作为值赋给不同的实体,为了防止由于共享对象而引起的副作用,最好是把它实现成不可变对象。
但是,实现不可变对象需要额外的开发工作量,在有些情况下可能还会导致对象过多的问题,所以得根据具体情况具体分析。
总之,并不总是要把值对象实现成不可变对象,是不是不可变对象也不是区分值对象的标准。
5.4服务
领域中的一些概念不能作为模型中的对象来处理,将领域需要的功能强制加给实体和值对象,不仅会破坏模型中对象的定义,而且还会人为的添加不必要的对象。
服务是领域模型中一类特殊的对象,它不代表领域中一个具本的东西,而是代表了一个领域中独立于实体与值对象之外的一些纯粹的功能。
好的服务具有以下三个对点:
1.与领域概念相关的操作不是实体和值对象中固有的部分
2.接口根据领域模型中的其它模型来定义
3.操作是无状态的
服务也是一个容易被滥用的概念,在贫血模型这咱错误的设计里面,就把所有的功能都放在服务中完成,而让实体和值对象不再拥有领域功能,变成了只包含数据的贫血变种。
服务应该具有确实的领域上意义,它可以代表一个外部系统给领域提供的一个功能,也可以代表了领域中一个在多个实体和值对象间协作完成的操作,而这个操作不属于任何一个实体或值对象。
当领域中的一些重要的进程或转换操作不是实体和值对象本身的职责时,把操作作为一种独立的接口加入模型,并声明为服务。
根据模型中使用的语言来定义接口,保证操作名是通用语言的一部分。
使这个服务变成无状态。
5.4.1服务和分隔的领域层
不仅要把服务与实体和值对象区分开来,还要把领域模型里的服务与其它层次里的服务区分开来。
领域模型里的服务完成了一个领域里的业务操作,它的名字来源于领域的通用语言,它的输入输出参数也是领域模型;
而其它层次的服务则完成了其它层次内的一个具体的功能。
比如,通知客户订购成功,通知哪个客户的哪个订购,通知他哪些事实,这是领域上的服务要完成的,而用邮件还是短信来通知,如何发送邮件或者短信等则是基础结构层要完成的。
5.4.2粒度
由于实体和值对象所实现在领域功能往往粒度比较细,如果直接让应用层调用这些实体和值对象,会过多暴露领域层的内部实现,会让应用层使用不方便,那么在领域层用一个服务来把这些操作包装成一个粒度较大的操作也是合理的。
这时候应该给这个服务层取个有领域意义的名字,并加入到通用语言当中。
5.5模型(包)
每个人都使用模块,但是很少有人将它们看作是模型中一个完整的部分。
在开发人员使用的技术框架中,代码被分门别类地进行划分。
甚至那些地经常重构模块的开发人员,也常常使用以前项目中设计好的模块。
的确应该实现模块间低关联与模块内的高内聚。
对于关连和内聚的解释,听上去往往像是技术上的衡量标准,用来机械地判断模块相互联系和相互作用的分布情况。
除代码外还将概念都分成了模块。
一个人在某个时刻所能考虑到事情是有限的(低关联)。
不连贯的思维是很难被理解的,也很难表现出统一的想法(高内聚)。
选择的模块应该能够描述系统,包含的概念集应该是内聚的。
这样通常会在模块间产生松散关联,如果不是,就想办法改变模型,重新整理概念。
或是寻找一种概念,构成模块的基础,以一种有意义的方式把元素集中起来。
寻找概念中的低关联,能够理解并解释它们之间的相互独立性。
对模型进行精练,直到能够根据高级的领域概念进行划分,同时分离相应的代码。
给模块命名,并把这些模块名加到通用语言中。
模块和它们的名称应该反映出对领域的理解。
模块也是领域驱动设计中模型的一个元素。
随着模型变得复杂,模型内的对象会变多,变得复杂,要想办法把这些对象划分到不现的模块中,否则到了一定的规模后,模型会复杂到难以掌握。
5.5.1敏捷的模块
5.5.2基础结构驱动打包的缺陷
某些基础结构的框架,往往会对打包有要求,如果模块的划分打包的方式由这些框架来驱动,那么很可能导致本来属于同一个模块的领域对象的代码被分散到了不同的包中,让模型代码变得难以理解。
除非真的是想将代码分布到不同的机器中,否则将实现一个概念对象的所有做代码都包括在同一个模块中,即使这些代码实现的不是同一个对象。
使用打包技术从其他代码中分离领域层。
否则,尽可能地让领域开发人员根据他们的模型和设计来打包领域对象。
5.6建模范式
除了面向对象之外,还有其它的建模范式,比如面向规则,面向流程,等等。
面向对象是实现领域模型设计的一个成熟可靠的范式,但对于某些特殊的领域,也不排斥人们使用其它的范式来对领域建模。
如果在同一个项目中需要使用多个建模范式,也会使项目变得复杂,领域模型不容易掌握。
因此,对于大部分的情况,还是应该多挖掘面向对象的能力,使用面向对象来描述领域。
《领域驱动设计》读书笔记(4)——领域对象的生命周期
用来给领域建模的模型要素实体,值对象和服务等对象总有个创建使用销毁等的过程,尤其是实体和值对象,由于其要保存领域中的一些状态数据,往往还需要持久化到数所库等外部持久化设备中。
第六章就主要论述这方面的内容,给出了聚合,工厂和仓库等对象。
第六章领域对象的生命周期
在程序运行的过程中,大部分的对象都是在被创建出来之后很快就被垃圾回收了,但是领域对象,如实体和值对象往往具有复杂的生命周期。
它们不仅被创建回收,还会被持久化,从持久化设备中重建等。
而且这些对象之间并不是孤立的,它们之间存在着天然的关联,这些对象之间要在生命周期的每一个时刻都满足一些不变量。
对象之间的关联,以及它们之间的不变量来源于领域,比如说订单就有很多订单明细,而订单的总金额就是明细的金额之和。
如何管理这些对象给我们提出了挑战,处理不好就很容易使我们的工作偏离模型驱动的方向。
挑战可以分为两类:
∙在生命周期中维护对象的完整性
∙避免模型由于管理生命周期的复杂性而陷入困境
本章介绍了三种模式用来解决这个问题。
6.1聚合
对象之间的关联越少,维护对象的代价就越低,所以在设计中应该尽量减少对象之间的关联。
然而,现实的领域对象往往都会有着复杂的关联,因为领域本身就是这样的。
在包含着复杂关联的模型中,要保证对象修改的一致性是很困难的。
我们必须保证紧密关联的对象组也能保持不变性,而不仅仅是保证各个离散的对象。
如果锁定策略过于谨慎,就会导致多个用户毫无必要的相互干扰,使系统变得无法使用。
我们需要一种抽象机制用于在模型中对引用进行封装。
一个聚合是一簇相关联的对象,出于数据变化的目的,我们将这些对象视为一个单元。
每个聚合都有一个根和一个边界。
边界定义了根中包含什么;
根是包含在聚合中的单个特定的实体。
根是聚合中惟一允许被外部对象引用的元素,但在聚合的边界内,对象之间可以互相引用。
根之外的实体具有本地标识,但是它们仅仅在聚合内部才需要区分其标识,因为根实体上下文以外的对象不会看到它们。
不变量是指无论何时数据发生变化都必须满足的一致性原则。
聚合的成员之间可能存在着不变量关系。
我们不能指望涉及到聚合的所有规则是每时每刻都被满足的,一些依赖关系只能在某些特定的时刻,通过事件处理、批处理或其他更新机制来解决。
但是,聚合内部的不变量必须是在每次事务完成时就被满足的。
现在,为了将概念上的聚合转换为实现,我们需要在所有的事务中应用一系列的规则。
∙根实体具有全局标识,并最终负责对不变量的检查。
∙根实体具有全局标识。
边界内的实体具有本地标识,这些标识仅在聚合内部是惟一的。
∙聚合边界以外的任何对象除了可以引用根
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- 领域驱动设计笔记 by DN 领域 驱动 设计 笔记