作者:[美]埃里克 埃文斯
读后感
上次读完InfoQ的简洁版后,觉得很多知识还不了解,就找了这本书。这本书是DDD的创始人写的,篇幅比较长。读完之后,我个人的建议是可以不读。原因如下:
项目成本升高。DDD需要在整个项目开发周期中,不断的重构、优化,这又是随着对领域理解加深必须做的事情。这个成本很高,某种程度上很难保证项目按时完成。
人员素质要求高。需要参与项目的人员,对DDD有较深的理解,而且大家需要达成一致。但有些事情,往往是一千个读者,一千个哈姆雷特。自己觉得合理的设计未必是合适的设计。
DDD比较适合大型项目。项目较小,DDD未必合适。
DDD作为一个方法论,有很多不错的理论值得学习,如通用语言、精炼方案、组织合作模式等,从这个方面看,如果大家有兴趣,可以读一下、练习一下。
读完这本书,我最大的一个感慨是:代码是有灵魂的 - 他和人一样,有胖有瘦,有喜怒哀乐。所以编码前要做好设计,确保最终生成的是一个美人。
下面是读这本书的时候做的笔记,其中有部分理解了,有部分还没有完全理解,另外作者写的内容有时候真的很难提炼出名词解释
第一部分 运用领域模型
第1章 消化知识
和领域专家聊天,把需求聊清楚
第2章 交流与语言的使用
通用语言/UBIQUITOUS LANGUAGE
名词解释:一种通用的、共享的团队语言,项目所有相关方都用这种语言交流
作用:使得沟通顺畅,加强知识消化和对领域的深刻了解
实现:将模型作为语言的支柱。确保团队在内部的所有交流中以及代码中坚持使用这种语言。在画图、写东西,特别是讲话时也要使用这种语言。需要不断优化,记住对UBIQUITOUS LANGUAGE的更改就是对模型的更改。
第3章 绑定模型和实现
模型和代码是对应的,模型是代码的表现形式,代码是模型的表现形式。
软件开发于是就成了一个不断精化模型、设计和代码的统一的迭代过程。
模型驱动设计/MODEL-DRIVEN DESIGN
名词解释:MODEL-DRIVEN DESIGN(模型驱动设计)将分析模型和程序设计分视为统一体,寻求一种能够满足这两方面需求的单一模型。
作用:确保分析模型和程序设计的一致,使分析和设计活动中所获得的知识彼此共享,一起推动模型的精化和对领域理解的加深。尤其在分析和设计是两个团队的时候尤为重要。
实现:
- 运用由软件工具支持的建模范式,它可以在程序中直接创建模型中的对应概念。
- 另外也需要HANDS-ON MODELER(亲身实践的建模者)。
- 一方改变另一方也随之改变
第二部分 模型驱动设计的构造块
这些构造块能够保证软件实现得简洁并且与模型保持一致
第4章 分离领域
####分层架构/LAYEREDARCHITECTURE
名词解释:层中的任何元素都仅依赖于本层的其他元素或其下层的元素。向上的通信必须通过间接的方式进行
作用:将领域对象与系统中的其他功能分离,这样就能够避免将领域概念和其他只与软件技术相关的概念搞混,也不会在纷繁芜杂的系统中完全迷失了领域。分层的价值在于每一层都只代表程序中的某一特定方面。这种限制使每个方面的设计都更具内聚性,更容易解释
实现:
第5章 软件中所表示的模型
本章主要讲述如何在软件设计中将模型的各个元素表现出来
关联
名词解释:对象之间的相互关系
简化关联作用:大量的关联会使实现和维护变得很复杂
简化关联实现:
- 规定一个遍历方向。
- 添加一个限定符,以便有效地减少多重关联。
- 消除不必要的关联。
实体/ENTITY
名词解释:主要由标识定义的对象被称作ENTITY。
作用:ENTITY最基本的职责是确保连续性,以便使其行为更清楚且可预测。
实现:ENTITY可以是任何事物,只要满足两个条件即可
- 它在整个生命周期中具有连续性
- 它的区别并不是由那些对用户非常重要的属性决定的。
值对象/VALUE OBJECT
名词解释:用于描述领域的某个方面而本身没有概念标识的对象称为VALUE OBJECT(值对象)
作用:不关心使用的是VALUE OBJECT的哪个实例。由于不受这方面的约束,设计可以获得更大的自由,因此可以简化设计或优化性能。
实现:对象没有概念上的标识,它们描述了一个事务的某种特征
服务/SERVICE
名词解释:在某些情况下,最清楚、最实用的设计会包含一些特殊的操作,这些操作从概念上讲不属于任何对象。与其把它们强制地归于哪一类,不如顺其自然地在模型中引入一种新的元素,这就是SERVICE(服务)。
作用:SERVICE是作为接口提供的一种操作,它在模型中是独立的,它不像ENTITY和VALUEOBJECT那样具有封装的状态。它强调的是与其他对象的关系,只是定义了能够为客户做什么。
实现:好的SERVICE有以下3个特征。
- 与领域概念相关的操作不是ENTITY或VALUE OBJECT的一个自然组成部分。
- 接口是根据领域模型的其他元素定义的。
- 操作是无状态的。
模块/MODULE/PACKAGE
名词解释:将那些具有紧密概念关系的模型元素集中到一起
作用:能够解决“认知超载”问题。另外MODULE为人们提供了两种观察模型的方式,一是可以在MODULE中查看细节,而不会被整个模型淹没,二是观察MODULE之间的关系,而不考虑其内部细节。
实现:在MODULE选择的早期,有些错误是不可避免的,这些错误导致了高耦合,从而使MODULE很难进行重构。而缺乏重构又会导致问题变得更加严重。克服这一问题的唯一方法是接受挑战,仔细地分析问题的要害所在,并据此重新组织MODULE。
建模范式
主流的范式是面向对象设计,而且现在的大部分复杂项目都开始使用对象
第6章 领域对象的生命周期
本章主要讲述如何维护对象的生命周期完整性,防止模型陷入管理生命周期复杂性造成的困境当中。
聚合/AGGREGATE
名词解释:AGGREGATE就是一组相关对象的集合,我们把它作为数据修改的单元
作用:AGGREGATE划分出一个范围,在这个范围内,生命周期的每个阶段都必须满足一些固定规则。它通过定义清晰的所属关系和边界,并避免混乱、错综复杂的对象关系网来实现模型的内聚。
实现:每个AGGREGATE都有一个根(root)和一个边界(boundary)。边界定义了AGGREGATE的内部都有什么。根则是AGGREGATE所包含的一个特定ENTITY。对AGGREGATE而言,外部对象只可以引用根,而边界内部的对象之间则可以互相引用。
工厂/FACTORY
名词解释:可以帮助封装复杂的对象创建过程,它就是工厂
作用:当创建一个对象或创建整个AGGREGATE时,如果创建工作很复杂,或者暴露了过多的内部结构,则可以使用FACTORY进行封装。
实现:
- 每个创建方法都是原子的,而且要保证被创建对象或AGGREGATE的所有固定规则。
- FACTORY应该被抽象为所需的类型,而不是所要创建的具体类
资源库/REPOSITORY
名词解释:REPOSITORY将某种类型的所有对象表示为一个概念集合(通常是模拟的)。它的行为类似于集合(collection),只是具有更复杂的查询功能。
作用:
- 为客户提供了一个简单的模型,可用来获取持久化对象并管理它们的生命周期;
- 它们使应用程序和领域设计与持久化技术(多种数据库策略甚至是多个数据源)解耦;
- 它们体现了有关对象访问的设计决策;
- 可以很容易将它们替换为“哑实现”(dummy implementation),以便在测试中使用(通常使用内存中的集合)。
实现:向客户隐藏所有内部工作细节,REPOSITORY将会委托相应的基础设施服务来完成工作。将存储、检索和查询机制封装起来是REPOSITORY实现的最基本的特性
第7章 使用语言:一个扩展的示例
这一章是练习题,可以自己实现,检验学习成果
第三部分 通过重构来加深理解
本部分将会对找到深层次的模型加以说明并详细描述其实现过程,同时也会解释某些设计原则和模式
第8章 突破
突破
名词解释:通过持续重构,有些最重要的理解也会突然出现,给整个项目带来巨大的冲击
作用:其功能性及说明性急速增强,而复杂性却随之消失,产生深层模型
实现:不要试图去制造突破,那只会使项目陷入困境。通常,只有在实现了许多适度的重构后才有可能出现突破。
第9章 将隐式概念转变为显式概念
概念挖掘
名词解释:识别出某个重要概念
作用:为了完成深层建模的第一步,设法在模型中表达出领域的基本概念,必须先挖掘到概念
实现:需要开发人员去倾听团队语言、仔细检查设计中的不足之处以及与专家观点相矛盾的地方、研究领域相关文献并且进行大量的实验
概念建模
名词解释:将挖掘到的概念在模型中显式地表现出来
作用:让设计变得更加清晰深刻
实现:
- 显式的约束
- 将过程建模为领域对象
- 规格/SPECIFICATION: 是一个谓词,可用来确定对象是否满足某些标准。
第10章 柔性设计
本章阐述如何实现柔性设计:项目不会由于它自己的老化停滞不前,设计必须要让人们乐于使用,而且易于做出修改
释意接口/INTENTION-REVEALING INTERFACE
名词解释:每个元素的名称都揭示设计意图。类型名称、方法名称和参数名称组合在一起,共同形成了一个INTENTION-REVEALING INTERFACE(释意接口)
作用:成员可以迅速推断出它们的意义
实现:在命名类和操作时要描述它们的效果和目的,而不要表露它们是通过何种方式达到目的的。
无副作用函数/SIDE-EFFECT-FREE FUNCTION
名词解释:操作不会产生影响系统状态的改变
作用:开发人员就可以在无需理解其实现细节的情况下使用它
实现:
- 可以把命令和查询严格地放在不同的操作中
- 有一些替代的模型和设计,它们不要求对现有对象做任何修改
断言/ASSERTION
名词解释:编程术语,表示为一些布尔表达式,程序员相信在程序中的某个特定点该表达式值为真
作用:可以把副作用明确地表示出来,使它们更易于处理
实现:描述
- “后臵条件”描述了一个操作的副作用,也就是调用一个方法之后必然会发生的结果。
- “前臵条件”就像是合同条款,即为了满足后臵条件而必须要满足的前臵条件
概念轮廓/CONCEPTUAL CONTOUR
名词解释:模型已经与领域相吻合
作用:使模型的各个部分变得更稳定,也使得这些单元更直观,更易于使用和组合
实现:在连续的重构过程中观察发生变化和保证稳定的规律性,并寻找能够解释这些变化模式的底层CONCEPTUAL CONTOUR
独立的类/STANDALONE CLASS
名词解释:消除所有不重要的依赖的类
作用:独立类都极大地减轻了因理解MODULE而带来的负担
实现:尽力把最复杂的计算提取到STANDALONE CLASS(独立的类)中,实现此目的的一种方法是从存在大量依赖的类中将VALUE OBJECT建模出来。
闭合操作/CLOSURE OF OPERATION
名词解释:当我们对集合中的任意两个元素组合时,结果仍在这个集合中,这就叫做闭合操作。
作用:闭合的性质极大地简化了对操作的理解,而且闭合操作的链接或组合也很容易理解
实现:在定义操作时让它的返回类型与其参数的类型相同
声明式设计
名词解释:声明式设计对于不同的人来说具有不同的意义,但通常是指一种编程方式——把程序或程序的一部分写成一种可执行的规格(specification)。
作用:用一个范围非常窄的框架来自动处理设计中某个特别单调且易出错的方面,如持久化和对象关系映射。最好的声明式设计能够使开发人员不必去做那些单调乏味的工作,同时又完全不限制他们的设计自由。
实现:领域特定语言是一种有趣的方法,它有时也是一种声明式语言
第11章 应用分析模式
分析模式
名词解释:分析模式是一种概念集合,用来表示业务建模中的常见结构。它可能只与一个领域有关,也可能跨越多个领域。
作用:分析模式的最大作用是借鉴其他项目的经验,把那些项目中有关设计方向和实现结果的广泛讨论与当前模型的理解结合起来。
实现:看书看资料,查找有用思想
第12章 将设计模式应用于模型
设计模式可以应用到模型上,但是必须能够表达出领域概念。把设计模式用作领域模式的唯一要求是这些模式能够描述关于概念领域的一些事情,而不仅仅是作为解决技术问题的技术解决方案。
为了在领域驱动设计中充分利用这些模式,我们必须同时从两个角度看待它们:从代码的角度来看它们是技术设计模式,从模型的角度来看它们就是概念模式。
第13章 通过重构得到更深层的理解
通过重构得到更深层理解是一个持续不断的过程。
- 人们发现一些隐含的概念,并把它们明确地表示出来。
- 有些设计部分变得更具有柔性,或许还采用了声明式的风格。
- 开发工作一下子到了突破的边缘,然后开发人员跨越这条界线,得到了一个更深层的模型
- 接下来又重新开始了稳步的改进过程。
第四部分 战略设计
战略设计
名词解释:战略设计原则必须指导设计决策,以便减少各个部分之间的互相依赖,在使设计意图更为清晰的同时而又不失去关键的互操作性和协同性。战略设计原则必须把模型的重点放在捕获系统的概念核心,也就是系统的“远景”上。而且在完成这些目标的同时又不能为项目带来麻烦
作用:可以对非常复杂的系统进行建模
实现:上下文、精炼和大型结构
第14章 保持模型的完整性
模型完整性:模型最基本的要求是它应该保持内部一致,术语总具有相同的意义,并且不包含互相矛盾的规则
本章将介绍一些用于识别、沟通和选择模型边界及关系的技术。
边界上下文/BOUNDED CONTEXT
名词解释:明确地定义模型的范围——模型的范围是软件系统中一个有界的部分,这部分只应用一个模型,并尽可能使其保持统一
作用:BOUNDED CONTEXT明确地限定了模型的应用范围,以便让团队成员对什么应该保持一致以及上下文之间如何关联有一个明确和共同的理解。
实现:根据团队的组织、软件系统的各个部分的用法以及物理表现(代码和数据库模式等)来设置模型的边界
持续集成/CONTINUOUS INTEGRATION
名词解释:CONTINUOUS INTEGRATION是指把一个上下文中的所有工作足够频繁地合并到一起,并使它们保持一致,以便当模型发生分裂时,可以迅速发现并纠正问题
作用:防止模型发生分裂,难以保持集成度和一致性
实现:
- 分步集成,采用可重现的合并/构建技术;
- 自动测试套件;
- 有一些规则,用来为那些尚未集成的改动设臵一个相当小的生命期上限。
- 在讨论模型和应用程序时要坚持使用UBIQUITOUS LANGUAGE。
上下文映射/CONTEXT MAP
名词解释:描绘不同的界定上下文和它们之间关系的一份文档
作用:在项目中创建一个所有模型上下文的全局视图,可以减少混乱。团队就可以掌控模型的统一过程,并把不同的模型连接起来。
实现:
- BOUNDED CONTEXT应该有名称,以便可以讨论它们。
- 每个人都应该知道边界在哪里,而且应该能够分辨出任何代码段的CONTEXT,或任何情况的CONTEXT。
共享内核/SHARED KERNEL
名词解释:从领域模型中选出两个团队都同意共享的一个子集。一个团队在没与另一个团队商量的情况下不应擅自更改它。
作用:处理功能集成受到局限,CONTINUOUS INTEGRATION的开销非常高的情况
实现:共享内核中必须集成自动测试套件,因为修改共享内核时,必须要通过两个团队的所有测试。团队先修改各自的共享内核副本,然后每隔一段时间与另一个团队的修改进行集成.
客户/供应商关系/CUSTOMER/SUPPLIER DEVELOPMENT TEAM
名词解释:在两个团队之间建立一种明确的客户/供应商关系。下游团队相当于上游团队的客户。
作用:可以对开发过程进行组织,均衡地处理两个用户群的需求,并根据下游所需的特性来安排工作
实现:下游团队依赖于上游团队。下游团队的代表类似于用户代表,参加上游团队的计划会议,上游团队直接与他们的“客户”同仁讨论和权衡其所需的任务。
跟随者模式/CONFORMIST
名词解释:严格遵从上游团队的模型
作用:应对与一个对合作不感兴趣的团队进行集成
实现:上游设计的质量不是很差,而且风格也能兼容
防崩溃层/ANTICORRUPTION LAYER
名词解释:防崩溃层使用外部语言与外部模型交流,而不是客户端语言。这个层在两个领域和语言之间扮演双向转换器,它最大的好处在于可以使客户端模型保持纯洁和一致,不会受到外部模型的污染。
作用:在不同模型的关联部分之间建立转换机制,这样模型就不会被未经消化的外来模型元素所破坏。
实现:对ANTICORRUPTION LAYER设计进行组织的一种方法是把它实现为FACADE、ADAPTER和转换器的组合,外加两个系统之间进行对话所需的通信和传输机制。
各行其道/SEPARATE WAY
名词解释:如果两组功能之间的关系并非必不可少,那么二者完全可以彼此独立。
作用:集成总是代价高昂,而有时获益却很小。
实现:声明一个与其他上下文毫无关联的BOUNDED CONTEXT,使开发人员能够在这个小范围内找到简单、专用的解决方案。
开放主机/OPEN HOST
名词解释:定义一个协议,把你的子系统作为一组SERVICE供其他系统访问。
作用:当一个子系统必须与大量其他系统进行集成时,为每个集成都定制一个转换层可能会减慢团队的工作速度。
实现:开放这个协议,以便所有需要与你的子系统集成的人都可以使用它。当有新的集成需求时,就增强并扩展这个协议
发布的语言/PUBLISHED LANGUAGE
名词解释:把一个良好文档化的、能够表达出所需领域信息的共享语言作为公共的通信媒介,必要时在其他信息与该语言之间进行转换。
作用:处理当两个领域模型必须共存而且必须交换信息时,转换过程本身就可能很复杂,而且很难文档化和理解。
实现:不必从头重建,找一些是否已经存在的语言
第15章 精炼
精炼
名词解释:精炼是把一堆混杂在一起的组件分开的过程,以便通过某种形式从中提取出最重要的内容
作用:(1)帮助所有团队成员掌握系统的总体设计以及各部分如何协调工作;(2)找到一个具有适度规模的核心模型并把它添加到通用语言中,从而促进沟通;(3)指导重构;(4)专注于模型中最有价值的那部分;(5)指导外包、现成组件的使用以及任务委派。
实现:
核心域/CORE DOMAIN
名词解释:CORE DOMAIN是系统中最有价值的部分
作用:让一些决策变得容易,让开发者更关注重点
实现:对模型进行提炼。找到CORE DOMAIN并提供一种易于区分的方法把它与那些起辅助作用的模型和代码分开。最有价值和最专业的概念要轮廓分明。尽量压缩CORE DOMAIN。
普通子域/GENERIC SUBDOMAIN
名词解释:与项目意图无关的内聚子领域
作用:这些细节并不是我们的主要关注点,而只是起到支持作用
实现:把这些子领域的通用模型提取出来,并放到单独的MODULE中
领域愿景说明/DOMAIN VISION STATEMENT
名词解释:描述领域模型的本质,以及如何为企业带来价值
作用:可以用作一个指南,它帮助开发团队在精炼模型和代码的过程中保持统一的方向。
实现:写一份CORE DOMAIN的简短描述(大约一页纸)以及它将会创造的价值,也就是“价值主张”。
突出核心/HIGHLIGHTED CORE
名词解释:展示什么是具体核心模型元素
作用:使CORE DOMAIN很容易被分辨出来,可以增进沟通,并指导决策制定
实现:
- 精炼文档:编写一个非常简短的文档(3~7页,每页内容不必太多),用于描述CORE DOMAIN以及CORE元素之间的主要交互过程。
- 标明CORE
封装机制/COHESIVE MECHANISM
名词解释:把复杂算法隐藏到方法中,再为方法起一个一看就知道其用途的名字
作用:用来满足规则或者用来完成模型指定的计算。被分离出来的机制承担起支持的任务,从而留下一个更小的、表达得更清楚的COREDOMAIN
实现:把概念上的COHESIVE MECHANISM(内聚机制)分离到一个单独的轻量级框架中。要特别注意公式或那些有完备文档的算法。用一个INTENTION-REVEALING INTERFACE来暴露这个框架的功能
分离的核心/SEGREGATED CORE
名词解释:将核心域再次将强内聚性。对模型进行重构,把核心概念从支持性元素(包括定义得不清楚的那些元素)中分离出来,并增强CORE的内聚性,同时减少它与其他代码的耦合。把所有通用元素或支持性元素提取到其他对象中,并把这些对象放到其他的包中——即使这会把一些紧密耦合的元素分开。
作用:SEGREGATED CORE使我们能够提高CORE DOMAIN的内聚性
实现:通过重构得到SEGREGATED CORE的一般步骤如下所示。
- 识别出一个CORE子领域(可能是从精炼文档中得到的)
- 把相关的类移到新的MODULE中,并根据与这些类有关的概念为模块命名。
- 对代码进行重构,把那些不直接表示概念的数据和功能分离出来
- 对新的SEGREGATED CORE MODULE进行重构,使其中的关系和交互变得更简单、表达得更清楚
- 对另一个CORE子领域重复这个过程,直到完成SEGREGATED CORE的工作
抽象核心/ABSTRACT CORE
名词解释:提供了主要概念及其交互的简化视图
作用:可以精炼出一个更小、更内聚的CORE DOMAIN
实现:把模型中最基本的概念识别出来,并分离到不同的类、抽象类或接口中。设计这个抽象模型,使之能够表达出重要组件之间的大部分交互。把这个完整的抽象模型放到它自己的MODULE中,而专用的、详细的实现类则留在由子领域定义的MODULE中。
第16章 大型结构
大型结构
名词解释:“大型结构”是一种语言,人们可以用它来从大局上讨论和理解系统
作用:既能指导设计,又能帮助理解设计。另外,它还能够协调不同人员的工作,因为它提供了共享的整体视图,让人们知道各个部分在整体中的角色。
实现:它用一组高级概念或规则(或两者兼有)来为整个系统的设计建立一种模式
系统隐喻/SYSTEM METAPHOR
名词解释:是一种松散的、易于理解的大型结构,它与对象范式是协调的
作用:既能促进系统的交流,又能指导系统的开发
实现:当系统的一个具体类比正好符合团队成员对系统的想象,并且能够引导他们向着一个有用的方向进行思考时,就应该把这个类比用作一种大型结构
职责层/RESPONSIBILITY LAYER
名词解释:确定规则和目标是什么的层
作用:约束着其他层的行为
实现:注意观察模型中的概念依赖性,以及领域中不同部分的变化频率和变化的原因。如果在领域中发现了自然的层次结构,就把它们转换为宽泛的抽象职责。这些职责应该描述系统的高层目的和设计。对模型进行重构,使得每个领域对象、AGGREGATE和MODULE的职责都清晰地位于一个职责层当中。
知识级别/KNOWLEDGE LEVEL
名词解释:是一组描述了另一组对象应该有哪些行为的对象。
作用:KNOWLEDGE LEVEL分离了模型的这个自我定义的方面,并清楚地显示了它的限制
实现:创建一组不同的对象,用它们来描述和约束基本模型的结构和行为。把这些对象分为两个“级别”,一个是非常具体的级别,另一个级别则提供了一些可供用户或超级用户定制的规则和知识。
可插入式组件框架/PLUGGABLE COMPONENT FRAMEWORK
名词解释:所有组件都插入到一个中央hub上,这个hub支持组件所需的所有协议,并且知道如何与它们所提供的接口进行对话
作用:策略很容易共存
实现:从接口和交互中提炼出一个ABSTRACT CORE,并创建一个框架,这个框架要允许这些接口的各种不同实现被自由替换。同样,无论是什么应用程序,只要它严格地通过ABSTRACT CORE的接口进行操作,那么就可以允许它使用这些组件。