文章目录
- 第一部分 运用领域模型
- 第二部分 模型驱动设计的构造块
- 第四章 分离领域
- 第5章 软件中所表示的模型
- 第6章 领域对象的生命周期
- 第7章 使用语言:一个扩展的示例*
- 第三部分 通过重构来加深理解
- 第四部分 战略设计
- 第14章 保持模型的完整性
- 14.1 模式:BOUNDED CONTEXT
- 14.2 模式: CONTINUOUS INTERGRATION
- 14.3 模式:CONTEXT MAP
- 14.4 BOUNDED CONTEXT之间的关系*
- 14.5 模式:SHARED KERNEL
- 14.6 模式:CUSTOMER/SUPPLIER DEVLOPMENT TEAM
- 14.7 模式:CONFORMIST(跟随者)
- 14.8 模式:ANTICORRUPTION LAYER
- 14.8.3 一个关于防御的故事*
- 14.9 模式:SEPARATE WAY
- 14.10 模式:OPEN HOST SERVICE
- 14.11 模式:PUBLISHED LANGUAGE
- 14.12 大象的统一
- 14.13 选择你的模型上下文策略*
- 第15章 精炼
- 第16章 大型结构
- 第17章 领域驱动设计的综合运用
*** 阅读感
做这份笔记,也只是为了以后能看到这些简单的描述能快速回忆起相关知识点。
这本书相较于上本理论书,更多关注于实践的细节,用了很多的举例来帮助读者来消化吸收领域的相关概念和模式。针对上本书来说,它把一些难以理解的知识点掰开揉碎了讲解,举例更加生动形象。
阅读中,发现文中内容大部分都是在强调概念的可理解性与对开发设计原则的极致追求。把软件开发从无感情的堆叠实现转变为一种艺术创造工作,让开发者有更大的主观能动性去学习业务领域知识,对自己的代码更有责任感与自豪感。领域驱动设计太受细节设计的质量和实现决策的质量的影响,而且只要有少数几个开发人员没有弄清楚它们,整个项目就会偏离目标。
尽信书不如无书。如果照本宣科,那必然陷入魔障之中。我们可以学习其中的怎么让代码更容易阅读的技巧,怎么抽象沉淀平时所接触到的业务领域知识。
领域驱动,在我的实践中感受最明显的一点就是理论太过于理想化,也没有成熟的实践案例可供学习。不过利用其理论进行战略设计,能够非常有效地理解业务领域的知识,进行开发时,代码会非常简化且易于理解。但如果实际开发中完全遵循去实现书中的每一条理论,那么必会遇到很多很多的坑,没有熟练的使用经验是坚决不能把详细设计搬到公司项目开发中去的。
第一部分 运用领域模型
模型被用来描绘人们所关注的现实或想法的某个方面。模型是一种简化。它是对现实的解释——把与解决问题密切相关的方面抽象出来,而忽略无关的细节。
每个软件程序是为了执行用户的某项活动,或是满足用户的某种需求。这些用户应用软件的问题区域就是软件的领域。
模型正是解决此类信息超载问题的工具。模型这种知识形式对知识进行了选择性的简化和有意的结构化。适当的模型可以使人理解信息的意义,并专注于问题。
模型在领域驱动设计中的作用:
1、模型和设计的核心的互相影响。
2、模型是团队所有成员使用的通用语言的中枢
3、模型是浓缩的知识
软件的核心:
软件的核心是其为用户解决领域相关的问题的能力。
第1章 消化知识
在与客户沟通的几轮沟通建模的过程中,客户需要可以看到开发者如何将新学到的知识融合到模型中,然后反映到软件上。他们也可以从原型得到具体的反馈,从而印证自己的想法。
1.1 有效建模的要素
1、模型和实现的绑定
2、建立了一种基于模型的语言
3、开发一个蕴含丰富知识的模型
4、提炼模型
5、头脑风暴和实现
1.2 知识消化
高效的领域建模人员是知识的消化者,他们在大量信息中探寻有用的部分。他们不断尝试各种信息组织方式,努力寻找对大量信息有意义的简单视图。很多模型在尝试后被放弃或改造。只有找到一组适合于所有细节的抽象概念后,工作才算成功。
知识消化并非一项孤立的活动,它一般是在开发人员的领导下,由开发人员与领域专家组成的团队来共同协作。他们共同收集信息,并通过消化而将它组织为有用的形式。信息的原始资料来自领域专家头脑中的知识,现有系统的用户,以及技术团队以前在相关遗留系统或同领域的其他项目中积累的经验。
好的程序员会自然而然地抽象并开发出一个可以完成更多工作的模型。
在团队所有成员一起消化理解模型的过程中,他们之间的交互也会发生变化。领域模型的不断精化迫使开发人员学习重要的业务原理,而不是机械地进行功能开发。领域专家被迫提炼自己知道的重要知识的过程往往也是完善其自身理解的过程,而且他们会逐渐理解软件项目所必需的概念严谨性。
1.3 持续学习
当开始编写软件时,其实我们所知甚少。
项目知识零散地分散在很多人和文档中,其中夹杂着其他一些无关信息,因此我们甚至不知道那些知识时真正需要的知识。看起来没什么技术难度的领域很可能是一种错觉——我们并没意识到不知道的东西究竟有多少。这种无知往往会导致我们做出错误的假设。
同时,所有项目都会丢失知识。高效率的团队需要有意识地积累知识,并持续学习。那些善于自学的团队成员会成为团队的中坚力量,涉及最关键领域的开发任务要靠他们来攻克。
1.4 知识丰富的设计
模型获得的知识 远远不止是“发现名词”。业务活动和规则如何所涉及的实体一样,都是领域的核心,任何领域都有各种类别的概念。知识消化所产生的模型能够反映出对知识的深层理解。在模型发生改变的同时,开发人员对实现进行重构,一边反映出模型的变化,这样,新知识就被合并到应用程序中了。
1.5 深层模型
有用的模型很少停留在表面。随着对领域和应用程序需求的理解逐步加深,我们往往会丢弃那些最初看起来很重要的表面元素,或者切换它们的角度。这时,一些开始时不可能发现的巧妙抽象就会渐渐浮出水面,而它们恰恰切中问题的要害。
知识消化是一种探索,它永无止境。
第2章 交流与语言的使用
2.1 模式:UBIQUITOUS LANGUAGE
如果语言支离破碎,项目必将遭遇严重问题。领域专家使用他们自己的术语,而技术团队所使用的语言则经过调整,以便从设计角度讨论领域。
日常讨论所使用的术语与代码(软件项目的最重要产品)中使用的术语不一致。甚至同一个人在讲话和写东西时使用的语言也不一致,这导致的后果是,对领域的深刻表述常常稍纵即逝,根本无法记录到代码或文档中。
翻译使得沟通不畅,并削弱了知识消化。
然而任何一方的语言都不能成为公共语言,因为它们无法满足所有的需求。
UBIQUITOUS LANGUAGE(通用语言)的词汇包括类和主要操作的名称。
持续使用UBIQUITOUS LANGUAGE可以暴露模型中存在的缺点,这样团队就可以尝试并替换不恰当的术语或组合。当在语言中发现缺失时,新的词语被引入讨论中。这些语言上的更改也会在领域模型中引起相应的更改,并促使团队更新类图并重命名代码中的类和方法,当术语的意义改变时,甚至会导致行为也发生改变。
通过大量使用基于模型的语言,并且不达流畅绝不罢休,我们可以逐步得到一个完整的,易于理解的模型,它由简单元素组成,并通过组合这些简单元素表达复杂的概念。
将模型作为语言的支柱。确保团队在内部的所有交流中以及代码中坚持使用这种语言。在画图,写东西,特别是讲话时也要使用这种语言。
通过尝试不同的表示方法(它们反映了备选模型)来消除难点。然后重构代码,重新命名类、方法和模块,以便与新模型保持一致。解决交谈中的术语混淆问题,就像我们对普通词汇形成一致的理解一样。
要认识到,UBIQUITOUS LANGUAGE的更改就是对模型的更改。
领域专家应该抵制不合适或无法充分表达领域理解的术语或结构,开发人员应该密切关注那些将会妨碍设计的有歧义和不一致的地方。
2.2 "大声地"建模
人类本身颇具谈话的天赋。遗憾的是,当人们交谈时,通常并不使用领域模型的语言。
讨论系统时要结合模型。使用模型元素以及交互来大声描述场景,并且按照模型允许的方式将各种概念结合到一起。找到更简单的表达方式来讲出你要讲的话,然后将这些新的想法应用到图和代码中。
2.3 一个团队,一种语言
设计中有一些技术组件与领域专家无关,但模型的核心最好让他们参与。
如果连经验丰富的领域专家都不能理解模型,那么模型一定出了什么问题。
2.4 文档和图
简单、非正式的UML图能够维系整个讨论。绘制一幅包含当前问题的3~5个对象的图,这样每个人都可以集中注意力。
当人们必须通过UML图表示整个模型或设计时,麻烦也随之而来。
图是一种沟通和解释手段,它们可以促进头脑风暴。简洁的小图能够很好地实现这些目标,而涵盖整个对象模型的综合性大图反而失去了沟通或解释能力,因为它们将读者淹没在大量细节之中,加之这些图也缺乏目的性。鉴于此,我们应避免使用包罗万象的对象模型图,甚至不能使用包含所有细节的UML数据存储库。相反,应使用简化的图,图中只包含对象模型的重要概念。
2.4.1 书面设计文档
文档应作为代码和口头交流的补充。
文档应当鲜活并保持最新。
文档不应再重复表示代码已经明确表达出的内容。
2.4.2 完全依赖可执行代码的情况
良好的代码具有很强的表达能力,但它所传递的信息不能确保是准确的。一段代码所产生的实际行为是不会改变的。但是,方法名称可能会有歧义、会产生误导或者因为已经过时而无法表示方法的本质含义。
2.5 解释性模型
解释性模型可以包含那些提供上下文的领域方面——这些上下文用于澄清范围更窄的模型。
解释性模型提供了一定的自由度,可以专门为某一个特殊的主题定制一些表达力更强的风格。
解释性模型不必是对象模型,而且最好不是。
第3章 绑定模型和实现
3.1 模式:MODEL-DRIVEN DESIGN*
如果整个程序设计或者其核心部分没有与领域模型相对应,那么这个模型就是没有价值的,软件的正确性也值得怀疑。同时,模型和设计功能之间过于复杂的对应关系也是难于理解的,在实际项目中,当设计改变时也无法维护这种关系。若分析与和设计之间产生严重分歧,那么在分析和设计活动中所获得的知识就无法彼此共享。
模型驱动设计不再将分析模型和程序设计分离开,而是寻找一种能够满足这两方面需求的单一模型。不考虑纯粹的技术问题,程序设计中的每个对象都反映了模型中所描诉的相应概念。
软件系统各个部分的设计应该忠实地反映领域模型,以便体现出这两者之间的明确对应关系。我们应该反复检查并修改模型,以便软件可以更加自然地实现模型,即使想让模型反映出更深层次的领域概念时也应如此。我们需要的模型不但应该满足这两种需求,还应该能够支持健壮的通用语言。
从模型中获取用于程序设计和基本职责分配的术语。让程序代码成为模型的表述,代码的改变可能会是模型的改变,而其影响是必要波及接下来相应的项目活动。
完全依赖模型的实现通常需要支持建模范式的软件开发工作和语言,比如面向对象的编程。
3.2 建模范式和工具支持
为了使通用语言发挥作用,一定要在可控范围内严格保证模型与设计之间的一致性。要实现这种严格的一致性,必要要运用由软件工具支持的建模范式,它可以在程序中直接创建模型中的对应概念。
面向对象编程之所以功能强大,是因为它基于建模范式,并且为模型构造提供了实现方式。
3.3 揭示主旨:为什么模型对用户至关重要*
大部分的设计建议和例子都只针对将分析模型和设计模型分离的问题,但是这里的问题涉及了另外一对不同的模型:用户模型和设计/实现模型。
3.4 模式:HANDS-ON MODELER
3.4.1 两个基本要素:
1、模型要支持有效的实现。
2、抽象出关键的领域知识。
3.4.2 模型建立却从来没有派上用场的原因:
1、模型的一些意图在其传递过程中丢失了。
2、模型与程序实现及技术相互影响,而模型的设计者无法直接获得这种反馈,但开发软件已经自行编写出可以运行的程序——完全脱离了模型的设计,在使用模型的地方,仅仅是把它看作纯粹的数据结构。
如果编写代码的人员认为自己没必要对模型负责,或者不知道如何让模型为应用服务,那么这个模型就和程序没有任何关联。如果开发人员没有意识到改变代码就意味着改变模型,那么它们对程序的重构不但不会增强模型的作用,反而还会削弱它的效果。同样,如果建模人员不参与到程序实现的过程中,那么对程序实现的约束就没有切实的感受,即使有,也会很快忘记。如果分工阻断了设计人员和开发人员之间的协作,使他们无法转达实现MDDE L- DRVIEN DESIGN的种种细节,那么经验丰富的设计人员将不能将自己的知识和技术传递给开发人员。
第二部分 模型驱动设计的构造块
第四章 分离领域
4.1 模式: LAYERED ARCHITECTURE
要想创建出能够处理复杂任务的程序,需要做到关注点分离——使设计中的每个部分都得到单独的关注。在分离的同时,也需要维持系统内部复杂的交互关系。
给复杂的应用程序划分层次。在每一层内分别进行设计,使其具有内聚性并且只依赖于它的下层。采用标准的架构模式,只与上层进行松散的耦合。将所有与领域模型相关的代码放在一个层中,并把它与用户界面层,应用层以及基础设施层的代码分开。领域对象应该将重点放在如何表达领域模型上,而不需要考虑自己的显示和存储问题,也无需管理应用任务等内容。这使得模型的含义足够丰富,结构足够清晰,可以捕捉到基本的业务知识,并有效地使用这些知识。
4.1.1 将各层关联起来
各层之间是松散连接的,层与层之间的依赖关系只能是单向的。上层可以直接使用或操作下层元素,方法是通过调用下层元素的公共接口,保持对下层元素的引用(至少是暂时的),以及采用常规的交互手段。而如果下层元素需要与上层元素进行通信(不只是回应直接查询),则需要采用另一种通信机制,使用架构模式来连接上下层,如回调模式或OBSERVERS模式。
4.1.2 架构框架
当使用框架时,项目团队应该明确其使用目的:建立一种可以表达领域模型的实现并且用它来解决重要问题。项目团队必须想方设法让框架满足这些需求,即使着意味着抛弃框架中的一些功能。
4.2 领域层是模型的精髓
领域层是领域模型以及所有与其直接相关的设计元素的表现,它由业务逻辑的设计和实现组成。领域层的软件构造反映出模型概念。
4.3 模式:THE SMART UI 反模式
在页面实现所有的业务逻辑。将应用程序分成小的功能模块,分别将它们实现成用户界面,并在其中嵌入业务规则。用关系数据库作为共享的数据存储库。使用自动化程度最高的用户界面创建工具和可用的可视化编程工具。
4.4 其他分离方式
BOUNDED CONTEXT、ANTICORRUPTION LAYER
第5章 软件中所表示的模型
一个对象是用来表示某个具有连续性和标识的事物的呢,还是用于描述某种状态的属性呢?这时ENTITY与VALUE OBJECT之间最根本的区别。
领域中还有一些方面适合用动作或操作来表示,这比用对象表示更加清楚。这些方面最好用SERVIVCE来表示,而不应把操作的责任强加到ENTITY或VALUE OBJECT上。当对软件要做的某项无状态的活动进行建模时,就可以将该活动作为一项SERVICE。
5.1 关联
模型中每个可遍历的关联,软件中都要有同样属性的机制
现实生活中有大量“多对多”关联,其中有很多关联天生就是双向的。这些普遍的关联会使实现和维护变得很复杂。此外,它们也很少能表示出关系的本质。
至少有三种方法可以使得关联更易于控制。
1、在不要求双向遍历的关联上规定一个领域所倾向的方向
2、添加一个限定符,以便有效地减少多重关联。将多对多简化为一对多。
3、消除不必要的关联
5.2 模型:ENTITY(又称为REFERENCE OBJECT)
一些对象主要不是由它们的属性定义的。它们实际上表示了一条标识线,这条线跨越时间,而且常常经历多种不同的表示。有时,这样的对象必须与另一个具有不同属性的对象相匹配。而有时一个对象必须与具有相同属性的另一个对象区分开。错误的标识可能会破坏数据。
ENTITY可以是任何事物,只要满足两个条件即可,一是它在整个生命周期中具有连续性,二是它的区别并不是由那些对用户特别重要的属性决定。
当一个对象由其标识(而不是属性)区分时,那么在模型中应该主要通过标识来确定该对象的定义。使类定义变得简单,并集中关注生命周期的连续性和标识。定义一种区分每个对象的方式,这种方式应该与其形式和历史无关。要格外注意那些需要通过属性来匹配对象的需求。在定义标识操作时,要确保这种操作为每个对象生成唯一的结果,这可以通过附加一个保证唯一性的符号来实现。这种定义标识的方法可能来自外部,也可能是由系统创建的任意标识符,但它在模型中必须是唯一的标识。模型必须定义出“符合什么条件才算是相同的事物”。
5.2.1 ENTITY建模
ENTITY最基本的职责是确保连续性,以便使其行为更清楚且可预测。保持实体的简练是实现这一责任的关键。不要将注意力集中在属性或行为上,应该摆脱这些细枝末节,抓住ENTITY对象定义的最基本特征,尤其是那些用于识别、查找或匹配对象的特征上。只添加那些对概念至关重要的行为和这些行为所必需的属性。
5.2.2 设计标识操作
1、某些数据属性或属性组合可以确保它们在系统中具有唯一性,或者在这些属性上加一些简单约束可以使其具有唯一性。
2、为每个实例附加一个在类中唯一的符号(一个数字或字符串)
5.3 模式:VALUE OBJECT
用于描述领域某个方面而本身没有概念标识的对象被称为VALUE OBJECT。
VALUE OBJECT可以是其他对象的集合。
VALUE OBJECT甚至可以引用ENTITY。
当我们只关心一个模型元素的属性时,应把它归类为VALUE OBJECT。我们应该使这个模型元素能够表示出其属性的意义,并为它提供相关功能。VALUE OBJECT应该是不可变的。不要为它分配任何标识,而且不要把它设计成想ENTITY那么复杂。
5.3.1 设计VALUE OBJECT
在设计VALUE OBJECT时有多种选择,包含复制、共享或保持VALUE OBJECT不变。
复制和共享哪个更划算取决于实现环境。虽然复制有可能导致系统被大量的对象阻塞,但共享可能会减慢分布式系统的速度。
只要VALUE OBJECT是不可变的,变更管理就会很简单,因为除了整体替换之外没有其他的更改。
5.3.2 设计包含VALUE OBJECT的关联
应该尽量完全清除VALUE OBJECT之间的双向的关联。
5.4 模式:SERVICE
SERVICE是作为一个接口提供的一种操作,它在模型中是独立的,它不像ENTITY和VALUE OBJECT那样具有封装的状态。它强调的是与其他对象的关系,与ENTITY和VALUE OBJECT不同,它只是定义了能够为客户做什么。
好的SERVICE有以下3个特征:
1、与领域概念相关的操作不是ENTITY或VALUE OBJECT的一个自然组成部分。
2、接口是根据领域模型的其他元素定义的。
3、操作是无状态的。
当领域中的某个重要的过程或转换操作不是ENTITY或VALUE OBJECT的自然职责时,应该在模型中添加一个作为独立接口的操作,并将其声明为SERVICE。定义接口时要使用模型语言,并确保操作名称时UBIQUITOUS LANGUAE中的术语。此外,应该使SERVICE称为无状态的。
5.4.1 SERIVCE与孤立的领域层
这种模式只重视那些在领域中具有重要意义的SERVICE,但SERVICE并不只是在领域层中使用。纯技术的SERVICE,属于基础设施层。
在大多数开发系统中,在一个领域对象和外部资源之间直接建立一个借口是很别扭的,我们可以利用一个FACADE将这样的外部SERVICE包装起来,这个模型可能以模型作为输入,并返回一个“Funds Transfer”对象(作为它的结果)。
5.4.2 粒度
在大型系统中,中等粒度的、无状态的SERVICE更容易被复用。
5.4.3 对SERVICE的访问*
5.5 模式:MODULE(也称为PACKAGE)
MODULE为人们提供了两种观察模型的方式,一是可以在MODULE中查看细节,而不会被整个模型淹没,二是观察MODULE之间的关系,而不考虑内部细节。
MODULE作为一种更粗粒度的建模和设计元素,采用低耦合高内聚原则显得更为重要。
MODULE的名称应该是UBIQUITOUS LANGUAGE中的术语。MODULE及其名称应反映出领域的深层知识。
5.5.1 敏捷的MODULE
在不断开发过程中,有些错误是不可避免的,这些错误导致了高耦合,从而使MODULE很难进行重构。而缺乏重构又会导致问题变得更加严重。克服这一问题的唯一办法是接受挑战,仔细地分析问题的要害所在,并据此重新组织MODULE。
5.5.2 通过基础设施打包时存在的隐患
除非真的有必要将代码分布到不同服务器上,否则就把实现单一概念对象的所有代码放在同一个模块中(如果不能放在同一个对象中的话)
利用打包吧领域层从其他代码中分离出来。否则,就尽可能让领域开发人员自由地决定领域对象的打包方式,以便支持他们的模型和设计选择。
5.6 建模范式
MODEL-DRIVEN DESIGN要求使用一种与建模范式协调的实现技术。
5.6.1 对象范式流行的原因
对象建模的概念很简单,但它的丰富功能足以捕获重要的领域知识。
开发者社区和设计文化的成熟。
5.6.2 对象世界中的非对象
领域模型不一定是对象模型。
不管在项目中使用哪种主要的建模范式,领域中都会有一些部分更容易用某种其他范式来表达。
5.6.3 在混合范式中坚持使用MODEL- DRIVEN DESIGN
当将非对象元素混合到以面向对象为主的系统中时,需要遵循以下4条经验规则。
1、不要和实现范式对抗。
2、把通用语言作为依靠的基础。
3、不要一味地依赖UML。
4、保持怀疑态度。
第6章 领域对象的生命周期
主要的挑战有以下两类。
1、在整个生命周期内维护完整性。
2、防止模型陷入管理生命周期复杂性造成的困境当中。
6.1 模式:AGGREGATE
减少设计中的关联有助于简化对象之间的遍历,并在某种程度上限制关系的急剧增多。
AGGREGATE就是一组相关对象的集合,我们把它作为数据修改的单元。每个AGGREGATE都一个根(root)和一个边界(boundary)。边界定义了AGGERAGE的内部都有什么。根则是AGGREGATE所包含的一个特定ENTITY。对ARREGATE而言,外部对象只可以引用根,而边界内部的对象之间可以互相引用。
固定规则(invariant)是指在数据变化时必须保持的一致性规则,其涉及AGGEGATE成员之间的内部关系。而任何跨越AGGREGATE的规则将不要求每时每刻都保持最新状态。通过事件处理、批处理或其他更新机制,这些依赖会在一定时间内得以解决。但在每个事物完成时,AGGEGATE内部所应用的固定规则必须得到满足。
为了实现这个概念上的AGGREGATE,需要对所有事务应用一组规则:
1、根ENTITY具有全局标识,它最终检查固定规则。
2、根ENTITY具有全局标识。边界内的ENTITY具有本地标识,这些标识只在AGGREGATE内部才是唯一的。
3、AGGREGATE外部的对象不能引用除根ENTITY之外的任何内部对象。
4、只有AGGREGATE的根才能直接通过数据库查询获取。所有其他对象必须通过遍历关联来发现。
5、AGGREGATE内部的对象可以保持对其他AGGREGATE根的引用。
6、删除操作必须一次删除AGGREGATE边界之内的所有对象。
7、当提交对AGGREGATE边界内部的任何对象的修改时,整个AGGREGATE的所有固定规则都必须要被满足。
我们应该将ENTITY和VALUE OBJECT分门别类地聚集到AGGREGATE中,并定义每个AGGREGATE的边界。
6.2 模式:FACTORY
对象的功能主要体现在其复杂的内部配置以及关联方面。
对象的创建本身可以是一个主要操作,但被创建的对象并不适合承担复杂的装配操作。将这些职责混在一起可能产生难以理解的拙劣设计。让客户直接负责创建对象又会使客户的设计陷入混乱,并且破坏被装配对象或AGGREGATE的封装,而且导致客户与被创建对象之间产生过于紧密的耦合。
复杂的对象创建是领域层的职责,然而这项任务并不属于那些用于表示模型的对象。
应该将创建负责对象的实例和AGGREGATE的职责转移给单独的对象,这个对象本身可能没有承担领域模型中的职责,但它仍是领域设计的一部分。提供一个封装所有复杂装配操作的接口,而且这个接口不需要客户引用要被实力化的对象的具体类。在创建AGGREGATE时要把它作为一个整体,并确保它满足固定规则。
好的工厂需要满足以下两个基本需求:
1、每个创建方法都是原子的,而且要保证被创建对象或AGGREGATE的所有固定规则。
2、FACTORY应该被抽象为所需的类型,而不是所要创建的具体类。
6.2.1 选择FACTORY及其应用位置
1、如果需要向一个已存在的AGGREGATE添加元素,可以在AGGREGATE的根上创建一个FACTORY METHOD。
2、当一个对象的创建主要使用另一个对象的数据(或许还有规则)时,则可以在后者的对象上创建一个FACTORY METHOD。
6.2.2 有些情况下只需使用构造方法*
6.2.3 接口的设计
当设计FACTORY的方法签名时,无论是独立的FACTORY还是FACTORY METHOD,都要记住以下两点:
1、每个操作都必须是原子的。
2、Factory将与其参数耦合。
6.2.4 固定规则的相关逻辑应放在哪里
FACTORY负责确保它所创建的对象或AGGREGATE满足所有固定规则,然后在把应用于一个对象的规则移到该对象外部之前应三思。FACTORY可以将固定规则的检查工作委派给被创建对象,而且这通常是最佳选择。
6.2.5 ENTITY FACTORY与VALUE OBJECT FACTORY*
6.2.6 重建已存储的对象
用于重建对象的FACTORY与用于创建对象的FACTORY很类似,主要有以下两点不同:
1、用于重建对象的ENTITY FACTORY不分配新的跟踪ID。
2、当固定规则未被满足时,重建对象的FACTORY采用不同的方式进行处理。
6.3 模式: REPOSITORY
检索已存储对象实际上属于创建对象的范畴,因为从数据库中检索出来的数据要被用来组装新的对象。使用已存储的数据创建实例的过程称为重建
在所有持久化对象中,有一小部分必须通过基于对象属性的搜索来全局访问。当很难通过遍历方式来访问某些AGGREGATE根的时候,就需要通过这种访问方式。它们通常时ENTITY,有时是具有复杂内部结构的VALUE OBJECT,还可能是枚举VALUE。而其他对象则不宜使用这种访问方式,因为这会混淆它们之间的重要区别。随意的数据库查询会破坏领域对象的封装和AGGREGATE。技术基础设施和数据库访问机制的暴露会增加客户的复杂度,并妨碍模型驱动设计。
为每种需要全局访问的对象类型创建一个对象,这个对象相当于该类型的所有对象在内存中的一个集合的替身。通过一个众所周知的全局接口来提供访问。提供添加和删除对象的方法,用这些方法来封装在数据存储中实际插入或删除数据的操作。提供根据具体条件来挑选对象的方法,并返回属性值满足查询条件的对象或对象集合(所返回到的对象是完全实例化的),从而将实际的存储和查询技术封装起来。只为那些确实需要直接访问的AGGREGATE根提供REPOSITORY。让客户始终聚焦于模型,而将所有对象的存储和访问操作交给REPOSITORY。
6.3.1 REPOSITORY的查询
基于SPECIFICATION(规格)的查询时将REPOSITORY通用化的好办法。
6.3.2 客户代码可以忽视REPOSITORY的实现,但开发人员不能忽略*
6.3.3 REPOSITORY的实现
将存储、检索和查询机制封装起来是REPOSITORY实现的最基本的特性。
注意事项:
1、对类型进行抽象。
2、充分利用与客户解耦的优点。
3、将事务的控制权交给客户。
6.3.4 在框架内工作*
6.3.5 REPOSITORY与FACTORY的关系
FACTORY负责处理对象生命周期的开始,而REPOSITORY帮助管理生命周期的中间和结束。
6.4 为关系数据库设计对象*
第7章 使用语言:一个扩展的示例*
7.1 货物运输系统简介
7.2 隔离领域: 引入领域层
7.3 将ENTITY和VALUE OBJECT区别开
7.4 设计运输领域中的关联
7.5 ARRGEATE边界
7.6 选择REPOSITORY
7.7 场景走查
7.7.1 应用程序特性举例:更改Cargo的目的地
7.7.2 应用程序特性举例:重复业务
7.8 对象的创建
7.8.1 Cargo的FACTORY和构造函数
7.8.2 添加Handing Event
7.9 停一下,重构:Cargo AGGREGATE的另一种设计
7.10 运输模型中的MODULE
7.11 引入新特性:配额检查
7.11.1 连接两个系统
7.11.2 进一步完善模型:划分业务
7.11.3 性能优化
7.12 小结
第三部分 通过重构来加深理解
第8章 突破
重构的投入与回报并非呈线性关系。通常,小的调整会带来小的回报,小的改进也会积少成多。小改进可防止系统退化,成为避免模型变成陈腐的第一道防线。但是,有些最重要的理解也会突然出现,给整个项目带来巨大的冲击。
8.1 一个关于突破的故事*
8.1.1 华而不实的模型*
8.1.2 突破*
8.1.3 更深层模型*
8.1.4 冷静决策*
8.1.5 成果*
8.2 机遇
尽管我们希望进展顺利,但往往事与愿违。过渡到真正的深层模型需要从根本上调整思路,并且对设计做大幅修改。在很多项目中,建模和设计工作最重要的进展都来自于突破。
8.3 关注根本
要为突破做好准备,应专注于知识消化过程,同时也要逐渐建立健壮的UBIQUITOUS LANGUAGE。寻找那些重要的领域概念,并在模型中清晰地表达出来。
不要犹豫着不去做小的改进,这些改进即使脱离不开常规的概念框架,也可以逐渐加深我们对模型理解。不要因为好高骛远而使项目陷入困境,只要随时注意可能出现的技术就够了。
8.4 后记:越来越多的理解
第9章 将隐式概念转变为显式概念
深层建模的第一步就是要设法在模型中表达出领域的基本概念。随后,在不断消化知识和重构的过程中,实现模型的精化。
若开发人员识别出设计中隐含的某个概念或是在讨论中受到启发而发现一个新概念时,就会对领域模型和相应的代码进行许多的转换,在模型中加入一个或多个对象或关系,从而将此概念显式地表达出来。
9.1 概念挖掘
9.1.1 倾听语言
倾听领域专家使用的语言。有没有一些术语能够简洁地表达出复杂的概念?他们有没有纠正过你的用词(也许是很委婉的提醒)?当你使用某个特定的词语时,他们脸上是否已经不再流露出迷惑的表情?这些都暗示了某个概念也许可以改进模型。
UBIQUITOUS LANGUAGE是由遍布于对话、文档、模型图甚至代码中的词汇构成的。
9.1.2 检查不足之处
要挖掘的地方就是设计中最不足的地方,也就是操作复杂且难于解释的地方。每当有新的需求时,似乎都会让这个地方变得更复杂。
9.1.3 思考矛盾之处
要解决所有矛盾时不太现实的,甚至是不需要的。
9.1.4 查阅书籍
在很多领域中,你都可以找到解释基本概念和传统思想的书籍。你依然需要与领域专家合作,提炼与你问题相关的那部分知识,然后将其转化为适用于面向对象软件的概念。但是,查阅书籍也许能够使你一开始就形成一致且深层的认识。
9.1.5 尝试,再尝试
每次改变都会把开发人员更深刻的理解添加到模型中。每次重构都使设计变得更灵活并且为那些可能需要修改的地方做好准备。
9.2 如何为那些不太明显的概念建模
9.2.1 显式的约束
约束是模型概念中非常重要的类别。它们通常是隐含的,将它们显式地表现出来可以极大地提高设计质量。
如果约束的存在掩盖了对象的基本职责,或者如果约束在领域中非常突出但在模型中却不明显,那么就可以将其提取到一个显式的对象中,甚至可以把它建模为一个对象和关系的集合。
9.2.2 将过程建模为领域对象
首先要说明的是,我们都不希望过程变成模型的主要部分。对象是用来封装过程的,这样我们只需考虑对象的业务目的或意图就可以了。
约束和过程是两大类模型概念。
规格(specification)提供了用于表达特定类型的规则的精确方式,它把这些规则从条件逻辑中提取出来,并在模型中把它们显式地表达出来。
9.2.3 模式:SEPECIFICATION
业务规则通常不适合作为ENTITY或VALUE OBJECT的职责,而且规则的变化和组合也会掩盖领域对象的基本含义。但是将规则移出领域层的结果会更糟糕,因为这样一来,领域代码就不再表达模型了。
逻辑编程提供了一种概念,即“谓词”这种可分离、可组合的规则对象,但是要把这种概念用对象完全实现是很麻烦的。同时,这种概念过于通用,在表达设计意图放main,它的针对性不如专门的设计那么好。
SPECIFICATION就是一个谓词,可用来确定对象是否满足某些标准。
9.2.4 SPECIFICATION的应用和实现
SPECIFICATION最有价值的地方在于它可以将看起来完全不同的应用功能统一起来。出于以下3个目的中的一个或多个,我们可能需要制定对象的状态。
1、验证对象,检查它是否能满足某些需求或者是否已经为实现某个目标做好了准备。
2、从集合中选择一个对象。
3、指定在创建新对象时必须满足某种需求。
第10章 柔性设计
为了使项目能够随着开发工作的进行加速前进,而不会由于它自己的老化停滞不前,设计必须要让人们乐于使用,而且易于做出修改。这就是柔性设计。
10.1 模式:INTENTION_REVEALING INTERFACES
在命名类和操作时要描述它们的效果和目的,而不要表露它们是通过何种方式达到目的的。这样可以使客户开发人员不必去理解内部细节。这些名称应该与UBIQUITOUS LANGUAGE保持一致,以便团队成员可以迅速推断出它们的意义。在创建一个行为之前先为它编写一个测试,这样可以促使你站在客户开发人员的角度上来思考它。
10.2 模式:SIDE_EFFECT-FREE FUNCTION
尽可能把程序的逻辑放到函数中,因为函数是只返回结果而不产生明显副作用的操作。严格把命令(引起明显的状态改变的方法)隔离到不返回领域信息的、非常简单的操作中。当发现了一个非常适合承担复杂逻辑职责的概念时,就可以把这个复杂逻辑移到VALUE OBJECT中,这样可以进一步控制副作用。
10.3 模式:ASSERTION
把操作的后置条件和类及AGGREGATE的固定规则表述清楚。如果在你的编程语言中不能直接编写ASSERTION,那么就把它们编写成自动的单元测试。还可以把它们写到文档或图中(如果符合项目开发风格的话)。
寻找在概念上内聚的模型,以便使开发人员更容易推断出预期的ASSERTION,从而加快学习过程并避免代码矛盾。
10.4 模式:CONCEPTUAL CONTOUR(概念轮廓)
把设计元素 (操作、接口、类和AGGREGATE)分解为内聚的单元,在这个过程中,你对领域中一切重要划分的直观认识也要考虑在内。在连续的重构过程中观察发生变化和保证稳定的规律性,并寻找能够解释这些变化模型的底层CONCEPTUAL CONTOUR。使模型与领域中那些一致的方面(正是这些方面使得领域成为一个有用的知识体系)相匹配。
10.5 模式:STANDALONE CLASS(独立类)
低耦合是对象设计的一个基本要素。尽一切可能保持低耦合。把其他所有无关概念提取到对象之外。这样类就变得完全独立了,这就使得我们可以单独研究和理解它。每个这样的独立类都极大地减轻了因理解MODULE而带来的负担。
10.6 模式:CLAOSURE OF OPERATION
在适当的情况下,在定义操作时让它的返回类型与其参数的类型相同。如果实现者(implementer)的状态在计算中会被用到,那么实现者实际上就是操作的一个参数,因此参数和返回值应该与实现者有相同的类型。这样的操作就是在该类型的实例集合中的闭合操作。闭合操作提供了一个高层接口,同时又不会引入对其他概念的任何依赖。
10.7 声明式设计*
10.8 声明式设计风格*
10.9 切入问题的角度*
10.9.1 分割子领域*
10.9.2 尽可能利用已有的形式*
第11章 应用分析模式
分析模式并不是技术解决方案,它们只是些参考,用来指导人们设计特定领域中的模型。
分析模式的最大作用是借鉴其他项目的经验,把那些项目汇总有关设计方向和实现结果的广泛讨论与当前模型的理解结合起来。
当使用众所周知的分析模式中的术语时,一定要注意,不管其表面形式的变化有多大,都不要改变它所表示的基本概念。
很多对象模型都有文献资料可查,其中有些对象模型专门用于某个行业中的某种应用,而有些则是通用模型。大部分对象模型都有助于开阔思路,但只有为数不多的一些模型精辟地阐述了选择这些模式的原理和使用的结果,而这些才是分析模式的精华所在。
分析模式专注于一些最关键和最艰难的决策,并阐明了各种替代和选择方案。它们提前预测了一些后期结果。而如果单靠我们自己去发现这些结果,可能会付出高昂的代价。
第12章 将设计模式应用于模型
为了在领域驱动设计中充分利用设计模式,我们必须同时从两个角度看待它们:从代码的角度来看它们时技术设计模式,从模型的角度来看它们就是概念模式。
12.1 模式:STRATEGY(也称为POLICY)
我们需要把过程中易变部分提取到模型的一个单独的策略对象中。将规则与它所控制的行为区分开。按照STRATEGY设计模式来实现规则或可替换的过程。策略对象的多个版本表示来完成过程的不同方式。
12.2 模式:COMPOSITE
定义一个把COMPOSITE的所有成员都包含在内的抽象类型。在容器上实现那些查询信息的方法时,这些方法返回由容器内容所汇总的信息。而“叶”节点则基于它们自己的值来实现这些方法。客户只需使用抽象类型,而无需区分“叶”和容器。
12.3 为什么没有介绍FLYWEIGHT*
第13章 通过重构得到更深层的理解
有三件事是必须要关注的:
1、以领域为本。
2、用一种不同的方式来看待事务。
3、始终坚持与领域专家对话。
13.1 开始重构
即使在代码看上去很整洁的时候,也可能需要重构,原因是模型的语言没有与领域专家保持一致,或者新需求不能自然地添加到模型中。
重构的原因也可能来自学习:开发人员通过学习获得了更深刻的理解,从而发现了一个得到更清晰或更有用的模型的机会。
13.2 探索团队
修改的发起者会挑选几位开发人员一起工作,这些开发人员应该很擅长思考该类问题,了解领域,或者掌握深厚的建模技巧。如果涉及到一些难以捉摸的问题,它们还要请一位领域专家一起加入。
要保证修改的效率,需要注意几个关键事项:
1、自主决定;
2、注意范围和休息;
3、练习使用BUBIQUTOUS LANGUAGE;
13.3 借鉴先前的经验
我们没有必要总去做一些无谓的重复工作。用于查找缺失概念或改进模型的头脑风暴过程具有巨大的作用,通过这个过程可以收集来自各个方面的想法,并把这些想法与已有的知识结合起来。随着知识的消化的不断开展,就能找到当前问题的答案。
同样,当一种常见的形式体系(如算数逻辑或谓词逻辑)与领域的某个部分非常符合时,可以把这个部分提取出来,并根据它来修改形式系统的规则。
13.4 针对开发人员的设计*
13.5 重构的时机
在探索领域的过程中、在培训开发人员的过程中,以及在开发人员与领域专家进行思想交流的过程中,必须始终把“通过重构得到更深层理解”作为这些工作的一部分。因此,当发生以下情况时,就应该进行重构了:
1、设计没有表达出团队对领域的最新理解;
2、重要的概念被隐藏在设计中了(而且你已经发现了把它们呈现出来的方法);
3、发现了一个能令某个重要的设计部分变得更灵活的机会。
13.6 危机就是机遇*
第四部分 战略设计
第14章 保持模型的完整性
大型系统领域模型的完全统一既不可行,也不划算。
14.1 模式:BOUNDED CONTEXT
明确地定义模型所应用的上下文。根据团队的组织、软件系统的各个部分的用法以及物理表现(代码和数据库模式)来设置模型的边界。在这些边界中严格保持模型的一致性,而不要受到边界之外问题的干扰和混淆。
14.2 模式: CONTINUOUS INTERGRATION
建立一个把所有代码和其他实现工件频繁地合并到一起的过程,并通过自动化测试来快速查明模型的分裂问题。严格坚持使用UBIQUITOUS LANGUAGE,以便在不同人的头脑中演变出不同概念时,使所有人对模型都能达成一个共识。
14.3 模式:CONTEXT MAP
识别在项目中起作用的每个模型,并定义其BOUNDED CONTEXT。这包括非面向对象子系统的隐含模型。为每个BOUNDED CONTEXT命名,并把名称添加到UBIQUITOUS LANGUAGE中。
描述模型之间的联系点,明确所有通信需要的转换,并突出任何共享的内容。
先将当前的情况描绘出来。以后再做改变。
14.3.1 测试CONTEXT的边界*
14.3.2 CONTEXT MAP的组织和文档化
两个重点:
1、BOUNDED CONTEXT应该有名称,以便可以讨论它们。
2、每个人都应该知道边界在哪里,而且应该能够分辨出任何代码段的CONTEXT,或任何情况的CONTEXT。
14.4 BOUNDED CONTEXT之间的关系*
14.5 模式:SHARED KERNEL
从领域模型中选出两个团队都同意共享的一个子集。当然,除了这个模型子集以外,还包括与该模型部分相关的代码子集,或数据库设计的子集。这部分明确共享的内容具有特殊的地位,一个团队在没与另一个团队商量的情况下不应擅自更改它。
功能系统要经常进行集成,但集成的频率应该比团队汇总CONTINUOUS INTEGRATION的频率低一些。在进行这些集成的时候,两个团队都要运行测试。
SHARED KERNEL通常是CORE DOMAIN,或是一组GENERIC SUBDOMAIN(通用子领域),也可能是二者兼有,它可以是两个团队都需要的任何一部分模型。
14.6 模式:CUSTOMER/SUPPLIER DEVLOPMENT TEAM
在两个团队之间建立一种明确的客户/供应商关系。在计划会议中,下游团队相当于上游团队的客户。根据下游团队的需求来协商需要执行的任务并为这些任务做预算,以便每个人都知道双方的约定和进度。
两个团队共同开发自动化验收测试,用来验证预期的接口。把这些测试添加到上游团队的测试套件中,以便作为其持续集成的一部分来运行。这些测试使上游团队在做出修改时,不必担心对下游团队产生副作用。
14.7 模式:CONFORMIST(跟随者)
严格遵从上游团队的模型,可以消除在BOUNED CONTEXT之间进行转换的复杂性。尽管这会限制下游设计人员的风格,而且可能不会得到理想的应用程序模型,但选择CONFORMITY模型可以极大地简化集成。此外,这样还可以与供应商团队共享UBIQUITOUS LANGUAGE。供应商处于统治地位,因此最好使沟通变容易。他们从利他主义的角度出发,会与你分享信息。
14.8 模式:ANTICORRUPTION LAYER
创建一个隔离层,以便根据客户自己的领域模型来为客户提供相关功能。这个层通过另一个系统现有接口与其对话,而只需对那个系统做出很少的修改,甚至无需修改。在内部,这个层在两个模型之间进行必要的双向转换。
14.8.1 设计ANTICORRUPTION LAYER接口
ANTICORRUPTION LAYER的公共接口通常以一组SERVICE的形式出现,但偶尔也会采用ENTITY的形式。
14.8.2 实现ANTICORRUPTION LAYER
对ANTICORRUPTION LAYER设计进行组织的一种方法使把它实现为FACADE、ADAPTER。
14.8.3 一个关于防御的故事*
14.9 模式:SEPARATE WAY
集成总是代价高昂,而有时获益却很小。
声明一个与其他上下文毫无关联的BOUNDED CONTEXT,使开发人员能够在这个小范围内找到简单、专用的解决方案。
14.10 模式:OPEN HOST SERVICE
定义一个协议,把你的子系统作为一组SERVICE供其他系统访问。开放这个协议,以便所有需要与你的子系统集成的人都可以使用它。当有新的集成需求时,就增强并扩展这个协议,但个别团队的特殊需求除外。满足这种特殊需求的方式是使用一次性的转换器来扩产协议,以便使共享协议简单且内聚。
14.11 模式:PUBLISHED LANGUAGE
把一个良好文档化的、能够表达出所需领域信息的共享语言作为公共的通信媒介,必要时在其他信息与该语言之间进行转换。
14.12 大象的统一
1、抽象,把部分合并成整体。
2、去掉各个模型中那些偶然或不正确的方面,并创建新的概念。
14.13 选择你的模型上下文策略*
14.13.1 团队决策或更高层决策*
14.13.2 置身上下文中*
14.13.3 转换边界*
14.13.4 接受那些我们无法更改的事物:描述外部系统*
14.13.5 与外部系统的关系*
14.13.6 设计中的系统*
14.13.7 用不同模型满足特殊要求*
14.13.8 部署*
14.13.9 权衡*
14.13.10 当项目正在进行时*
第15章 精炼
精炼是把一堆混杂在一起的组件分开的过程,以便通过某种形式从中提取出最重要的内容,而这种形式将使它更有价值,也更有用。
鲸鱼模型的战略精炼包括以下部分:
1、帮助所有团队成员掌握系统的总体设计以及各部分如何协调工作;
2、找到一个具有适度规模的核心模型并把它添加到通用语言汇总,从而促进沟通;
3、指导重构;
4、专注于模型中最具有价值的那部分;
5、指导外包、现成组件的使用以及任务委派;
15.1 模式:CORE DOMAIN
在设计大型系统时,有非常多的组成部分——它们都很复杂而且对开发的成功也至关重要,但这导致真正的业务资产——领域模型最为精华的部分——被掩盖和忽略了。
一个严峻的现实是我们不可能对所有设计部分进行同等的精化,而是必须分出优先级。为了使领域模型成为有价值的资产,必须整齐地梳理出模型的真正核心,并完全根据这个核心来创建应用程序的功能。但本来就稀缺的高水平开发人员往往会把工作重心放在技术基础设施上,或者只是去解决那些不需要专门领域知识就能理解的领域问题(这些问题都已经有了很好的定义)。
真正体现应用程序价值并且使之成为业务资产的领域核心却通常是由那些技术水平稍差的开发人员完成的,他们与DBA一起创建数据模式,然后逐个特性编写代码,而根本没有对模型的概念能力加以任何利用。
对模型进行提炼。找到CORE DOMAIN并提供一种易于区分的方法把它与那些起辅助作用的模型和代码分开。最有价值和最专业的概念要概念分明。尽量压缩CORE DOMAIN。
让最有才能的人来开发CORE DOMAIN,并据此要求进行相应的招聘。在CORE DOMAIN中努力开发能够确保实现系统蓝图的深层模型和柔性设计。仔细判断任何其他部分的投入,看它是否能够支持这个提炼出的CORE。
15.1.1 选择工作核心
我们需要关注的是那些能够表示业务领域并解决业务问题的模型部分。
对CORE DOMAIN的选择取决于看问题的角度。
一个应用程序的CORE DOMAIN在另一个应用程序中可能只是通用的支持组件。
15.1.2 工作的分配
建立一支由开发人员和一位或多位领域专家组成的联合团队,其中开发人员必须能力很强、能够长期稳定地工作并且对学习领域知识非常感兴趣,而领域专家则要掌握深厚的业务知识。
从外界聘请一些短期的专业人员来设计CORE DOMAIN的关键环节是行不通的,因为团队需要积累领域知识,而且短期人员会造成知识的流失。相反,充当培训和指导角色的专家可能非常有价值,因为他们帮助团队建立境遇设计技巧,并促进团队成员使用尚未掌握的高级设计原则。
购买CORE DOMAIN也是行不通的。
自主开发的软件最大的价值来自于对CORE DOMAIN的完全控制。
15.2 精炼的逐步提升
一份简单的DOMAIN VISION STATEMENT(领域愿景说明)只需很少的投入,它传达了基本概念以及它们的价值。HIGHLIGHETED CORE(突出核心)可以增进沟通,并指导决策制定,这也只需对设计进行很少的改动甚至无需改动。
更积极的精炼方法是通过重构和重新打包显式地分离GENERIC SUBDOMAIN(通用子领域),然后单独进行处理。
更富雄心的精炼是ABSTRACT CORE(抽象内核),它用纯粹的形式表示了最基本的概念和关系。
15.3 模式:GENERIC SUBDOMAIN
识别出那些与项目意图无关的内聚子领域。把这些子领域的通用模型提取出来,并放到单独的MODULE中。任何专有的东西都不应该放在这些模块中。
把它们分离以后,在继续开发的过程中,它们的优先级应低于CORE DOMAIN的优先级,并且不要分派核心开发人员来完成这些任务(因为他们很少能够从这些任务中获得领域知识)。此外,还可以考虑为这些GENERIC SUBDOMAIN使用现成的解决方案或“公开发布的模型”(PUBLISHED MODEL)。
15.3.1 通用不等于可重用
重用确实会发生,但不一定总是代码重用。模型重用通常是更高级的重用。
假设你要自己实现代码(内部实现或外包出去),那么不要特别关注代码的可重用性。因为那样做会违反精炼的基本动机——我们应该尽可能把大部分精力投入到CORE DOMAIN工作中。
15.3.2 项目风险管理
敏捷过程通常要求通过尽早解决最具风险的任务来管理风险。
除非团队拥有精湛的技术并且对领域非常熟悉,否则第一个雏形系统应该以CORE DOMAIN的某个部分作为基础,不管它有多么简单。
15.4 模式:DOMAIN VISION STATEMENT
写一份CORE DOMAIN的简短描述(大约一页纸)以及它将会创造的价值,也就是价值主张。那些不能将你的领域模型与其他领域模型区分开的方面就不要写了。展示出领域模式是如何实现和均衡各方面的。这份描述要尽量精简。尽早把它写出来,随着新的理解随时修改它。
15.5 模式:HIGHLIGHETED CORE
15.5.1 精炼文档
15.5.2 标明CORE
15.6 模式:COHESIVE MECHANISM
15.6.1 GENERIC SUBDOMAIN与COHE-SIVE MECHANISM
15.6.2 MECHANISM是CORE DOMAIN一部分
15.7 通过精炼得到声明式风格
15.8 模式:SEGREATED CORE
15.8.1 创建SEGREGATED CORE的代价
15.8.2 不断发展演变的团队决策
15.9 模式:ABSTRACT CORE
15.10 深层模型精炼
15.11 选择重构目标
第16章 大型结构
在一个大的系统中,如果因为缺少一种全局性的原则而使人们无法根据元素在模式(这些模式被应用于整个设计)中的角色来解释这些元素,那么开发人员就会陷入“只见树木,不见森林”的境地。
“大型结构”是一种语言,人们可以用它来从大局上讨论和理解系统。
设计一种应用于整个系统的规则(或角色和关系)模式,使人们可以通过它在一定程度上了解各个部分在整体中所处的为主(即使是在不知道各个部分的详细职责的情况下)。
大部分大型结构都无法用UML来表示,而且也不需要这样做。
16.1 模式:EVOLVING ORDER
让这种概念上的大型结构随着应用程序一起演变,甚至可以变成一种完全不同的结构风格。不要依此过分限制详细的设计和模型决策,这些决策和模型决策必须在掌握了详细知识之后才能确定。
让这种概念上的大型结构随着应用程序一起演变,甚至可以变成一种完全不同的结构风格。不要依此过分限制详细的设计和模型决策,这些决策和模型必须在掌握了详细知识之后才能确定。
有时个别部分具有一些很自然且很有用的组织和表达方式,但这些方式并不适用于整体,因此施加全局规则会使这些部分的设计不够理想。
大型结构还能够为我们做设计决策提供捷径,虽然原则上也可以通过研究各个对象来做出这些决策,但实际上这会耗费太长时间,而且产生的结果可能不一致。
大型结构通常需要跨越BOUNDED CONTEXT来使用。
大型结构必须适应开发工作中的实际约束。
与CONTEXT MAP不同的是,大型结构是可选的。当使用某种结构可以节省成本并带来益处时,并且发现了一种恰当的结构,就应该使用它。实际上,如果一个系统简单到把它分解为MODULE就足以理解它,那么就不必使用这种结构了。
当发现一种大型结构可以明显使系统变得更清晰,而又没有对模型开发施加一些不自然的约束时,就应该采用这种结构。使用不合适的结构还不如不使用它,因此最好不要为了追求设计的完整性而去勉强去使用一种结构,而应该找到尽可能精简的方式解决所出现问题。要记住宁缺毋滥的原则。
16.2 模式:SYSTEM MERAPHOR
当系统的一个集体类正好符合团队成员对系统的想象,并且能够引导他们向着一个有用的方向进行思考时,就应该把这个类比用作一种大型结构。围绕这个隐喻来组织设计,并把它吸收到UBIQUITOUS LANGUAGE中。SYSTEM METAPHOR应该既能促进系统的交流,又能指导系统的开发。它可以增加系统不同部分之间的一致性,甚至可以跨越不同的BOUNDED CONTEXT。但所有隐喻都不是完全精确的,因此应不断检查隐喻是否过度或不恰当,当发现它起到妨碍作用时,要随时准备放弃它。
16.3 模式:RESPONSIBILITY LAYER
注意观察模型中的概念依赖性,以及领域中不同部分的变化频率和变化的原因。如果在领域中发现了自然的层次结构,就把它们转换为宽泛的抽象职责。这些职责应该描述系统的高层目的和设计。对模型进行重构,使得每个领域对象、AGGREGATE和MODULE的职责都清晰地位于一个职责层中。
16.4 模式:KNOWLEDGE LEVEL
如果在一个应用程序中,ENTITY的角色和它们之间的关系在不同的情况下有很大变化,那么复杂性会显著增加。在这种情况下,无论是一般的模型还是高度定制的模型,都无法满足用户的需求。为了兼顾各种不同的情形,对象需要引用其他的类型,或者需要具备一些在不同情况下包括不同使用方式的属性。具有相同数据和行为的类可能会大量增加,而这些类的唯一作用只是为了满足不同的组装规则。
创建一组不同的对象,用它们来描述和约束基本模型的结构和行为。把这些对象分为两个“级别”。一个是非常具体的级别,另一个级别则提供了一些可供用户或超级用户定制的规则和知识。
16.5 模式:PLUGGABLE COMPONENT FRAMEWORK
从接口和交互中提炼出一个ABSTRACT CORE,并创建一个框架,这个框架要允许这些接口的各种不同实现被自由替换。同样,无论是什么应用程序,只要它严格地通过ABSTRACR CORE的接口进行操作,那么就可以允许它使用这些组件。
16.6 结构应该有一种什么样的约束
通信必须是单向的,不能让较低层产生对较高层的依赖性。
通常低层对高层的信号传递是通过某种事件机制实现的。
每条结构规则都应该使开发变得更容易。
16.7 通过重构得到更恰当的结构
16.7.1 最小化
控制成本的一个关键是保持一种简单、轻量级的结构。不要试图使结构面面俱到。只需解决最主要的问题即可,其他问题可以留到后面一个一个地解决。
16.7.2 沟通和自律
术语表、大比例结构
整个团队在新的开发和重构中必须遵守结构。要做到这一点,整个团队必须理解这种结构,必须把术语和关系纳入到UBIQUITOUS LANGUAGE。
16.7.3 通过重构得到柔性设计*
16.7.4 通过精炼可以减轻负担*
第17章 领域驱动设计的综合运用
17.1 把大型结构与BOUNDED CONTEXT结合起来使用
战略设计的3个基本原则(上下文、精炼和大型结构)并不是可以互相代替的,而是互为补充,并且以多种方式进行互助。
17.2 将大型结构与精炼结合起来使用
大型结构和精炼的概念也是互为补充的。大型结构可以帮助解释CORE DOMAIN内部的关系以及GENERIC SUBDOMAIN之间的关系。
17.3 首先评估
当对一个项目进行战略设计时,首先需要清晰地评估现状。
1、画出CONTEXT MAP。
2、注意项目上的语言使用。
3、理解重点所在。
4、项目所采用的技术时遵循MODEL- DRIVEN DESIGN,还是与之相悖?
5、团队开发人员是否具备必要的技能?
6、开发人员是否了解领域知识?他们对领域是否感兴趣?
17.4 由谁制定策略
17.4.1 从应用结构开发自动得出的结构
从理论上,任何一对儿编程人员都可以根据自己的理解来完全自发地创建一种结构。策略设计领导者可能会从开发人员中自动出现几位。
17.4.2 以客户为中心的架构团队*
17.5 制定战略设计决策的6个要点
1、决策必须传达到整个团队。
2、决策过程必须收集反馈意见。
3、计划必须允许演变。
4、架构团队不必把所有最好、最聪明的人员都吸收进来。
5、战略设计需要遵守简约和谦逊的原则。
6、对象的职责要专一,而开发人员应该是多面手。
17.5.1 技术框架同样如此
不要编写傻瓜式的框架。
17.5.2 注意总体规划
提倡聚少成多地成长。