[水] 《领域驱动设计》关键段落词句(1)

2020.07.13

本文摘取《领域驱动设计》1-7章我认为有意义/重要的段落语句,并列出各节标题
大体来说,《领域驱动设计》1-7章提出了ubiquitous language 与model-driven design的概念,阐述了领域驱动设计的基本目标。通过给出Entity、Value Object、Factory等构造块,给出了设计时针对复杂问题进行抽象和分治的一系列有效手段。尤其是第7章的实例充分表现了各个构造块的作用和运用方式。

一、消化知识

1.1 有效建模的要素

1.2 知识消化

1.3 持续学习

1.4 知识丰富的设计

1.5 深层模型

二、交流和语言的使用

2.1 模式:Ubiquitous Language

所有翻译的开销,连带着误解的风险,成本实在太高了。项目需要一种公共语言,这种语言要比所有语言的最小公分母健壮得多。通过团队的一致努力,领域模型可以成为这种公共语言的核心,同时将团队沟通与软件实现紧密联系在一起。该语言将存在于团队工作中的方方面面。

将模型作为语言的支柱。确保团队在内部的所有交流中以及代码中坚持使用这种语言。在画图、写东西,特别是讲话时也要使用这种语言。

通过尝试不同的表示方法(他们反映了备选模型)来消除难点。然后重构代码,重新命名类、方法和模块,以便与新模型保持一致。解决交谈中的术语混淆问题,就像我们对普通词汇形成一致的理解一样。

要认识到,Ubiquitous Language的更改就是对模型的更改。

领域专家应该抵制不合适或无法充分表达领域理解的术语或结构,开发人员应该密切关注那些将会妨碍设计的有歧义和不一致的地方。

2.2 “大声地”建模

2.3 一个团队,一种语言

2.4 文档和图

2.5 解释性模型

三、绑定模型和实现

3.1 模式:Model-Driven Design

软件协同各个部分的设计应该忠实地反映领域模型,以便体现出两者之间的明确对应关系。我们应该反复检查并修改模型,以便软件可以更加自然地实现模型,即使想让模型反映出更深层次的领域概念时也应如此。我们需要的模型不但应该满足这两种需求,还应该能够支持健壮的Ubiquitous Language.

完全依赖模型的实现通常需要支持建模范式的软件开发工具和语言,比如面向对象的编程。

3.2 建模范式和工具支持

3.3 揭示主旨:为什么模型对用户至关重要

3.4 模式:Hands-On Modeler

Model-Driven Design利用模型来为应用程序解决问题。项目组通过知识消化将大量杂乱无章的信息提炼成实用的模型。而Model-Driven Design将模型和程序实现过程紧密结合。Ubiquitous Language则成为开发人员、领域专家和软件产品之间传递信息的渠道。

四、分离领域

4.1 模式:Layered Architecture

4.2 领域层是模型的精髓

4.3 模式:The Smart UI "反模式"

五、软件中所表示的模型

领域中还有一些方面适合用动作或操作来表示,这比用对象表示要更加清楚。这些方面最好用Service来表示,而不应把操作的责任强加到Entity或Value Object上,尽管这样做稍微违背了面向对象的建模传统。Service是应客户端请求来完成某事。在软件的技术层中有很多Service。在领域中也可以使用Service,当对软件要做的某项无状态的活动进行建模时,就可以将该活动作为一项Service。

5.1 关联

5.2 模式:Entity(Reference Object)

当一个对象由其标识(而不是属性)区分时,那么模型中应该主要痛殴标识来确定该对象的定义。使类的定义变得简单,并集中关注生命周期的连续性和标识。定义一种区分每个对象的方式,这种方式应该与其形式和历史无关。要格外注意那些需要通过属性来匹配对象的需求。在定义标识操作时,要确保这种操作为每个对象生成唯一的结果,这可以通过附加一个保证唯一性的符号来实现。这种定义标识的方法可能来自外部,也可能是由系统创建的任意标识符,但它在模型中必须是唯一的标识。模型必须定义出“符合什么条件才算是相同的事物”。

当对象属性没办法形成真正唯一键时,另一种经常用到的解决方案是为每个实例附加一个在类中唯一的符号(如一个数字或字符串)。一旦这个ID符号被创建并存储为Entity的一个属性,必须将它指定为不可变的。它必须永远不变,即使开发系统无法直接强制这条规则。例如,当对象被扁平化到数据库中或从数据库中重新创建时,ID属性应该保持不变。有时可以利用技术框架来实现此目的,但如果没有这样的框架,就需要通过工程纪律来约束。

5.3 模式:Value Object

当我们只关系一个模型元素的属性时,应把它归类为Value Object。我们应该使这个模型元素能够表示出其属性的意义,并为他提供相关功能。Value Object应该是不可变的。不要为它分配任何标识,而且不要把它设计成像Entity那么复杂。

在有些语言和环境中,可以将属性或对象声明为不可变的,但有些却不具备这种能力...这只是说明我们需要更多的约束机制来确保满足一些重要的规则(这些规则只有在实现中才是隐式的)。命名规则、精心准备的文档和大量讨论都可以强化这些需求。

如果一个Value的实现是可变的,那么就不能共享它。无论是否共享Value Object,在可能的情况下都要将他们设计为不可变的。

5.4 模式:Service

当领域中的某个重要的过程或转换操作不是Entity或Value Object的自然职责时,应该在模型中添加一个作为独立接口的操作,并将其声明为Service。定义接口时要使用模型语言,并确保操作名称是Ubiquitous Language中的术语。此外,应该使Service成为无状态的。

应用层Service和领域层Service可能很难区分。应用层负责通知的设置,而领域层负责确定是否满足临界值。

由于应用层负责对领域对象的行为进行协调,因此细粒度的领域对象可能会把领域层的知识泄漏到应用层中。这产生的结果是应用层不得不处理复杂的、细致的交互,从而使得领域知识蔓延到应用层或用户界面代码中,而领域层会丢失这些知识。明智地引入领域层服务有助于在应用层和领域层之间保持一条明确的界限。

5.5 模式:Module(Package)

像领域驱动设计中的其他元素一样,Module是一种表达机制。Module的选择应该取决于被划分到模块中的对象的意义。当你将一些类放到Module中时,相当于告诉下一位看到你的设计的开发人员要把这些类放到一起考虑。如果说模型讲述了一个故事,那么Module就是这个故事的各个章节。模块的名称表达了其意义。这些名称应该被添加到Ubiquitous Language中。

如果一个类确实依赖于另一个包中的某个类,而且本地Module对该Module并没有概念上的依赖关系,那么或许应该移动该类或者考虑重新组织Module.

5.6 建模范式

领域模型不一定是对象模型。例如,使用Prolog语言实现的Model-Driven Design,它的模型是由逻辑规则和事实构成的。模型范式为人们提供了思考领域的方式。这些领域的模型由范式塑造成型。结果就得到了遵守范式的模型,这样的模型可以用支持对应建模风格的工具来有效地实现。

混合使用不同的范式使得开发人员能够用最适当的风格对特殊概念进行建模。此外,大部分系统都必须使用一些非对象的技术基础设施,最常见的就是关系数据库。但是在使用不同的范式后,要想得到一个内聚的模型就比较难了,而且让不同的支持工具共存也较为复杂。

六、领域对象的生存周期

6.1 模式:Aggregate

Aggregate外部的对象不能引用除根Entity之外的任何内部对象。根Entity可以把对内部Entity的引用传递给它们,但这些对象只能临时使用这些引用,而不能保持引用。根可以把一个Value Object的副本传递给另一个对象,而不必关心它发生什么变化。

作为上一条规则的推论,只有Aggregate的根才能直接通过数据库查询获取,所有其他对象必须通过遍历关联来发现。

我们应该将Entity和Value Object分门别类地据街道Aggregate中,并定义每个Aggregate的边界。在每个Aggregate中,选择一个Entity作为根,并通过根来控制对边界内其他对象的所有访问。只允许外部对象保持对根的引用。对内部成员的临时引用可以被传递出去,但仅在一次操作中有效。由于根控制访问,因此不能绕过它来修改内部对象。这种设计有利于确保Aggregate中的对象满足所有固定规则,也可以确保在任何状态变化时Aggregate作为一个整体满足固定规则。

6.2 模式:Factory

6.3 模式:Repository

在所有持久化对象中,有一小部分必须通过基于对象属性的搜索来全局访问。当很难通过遍历方式来访问某些Aggregate根的时候,就需要使用这种访问方式。它们通常是Entity,有时是具有复杂内部结构的Value Object,还可能是枚举Value。而其他对象则不宜使用这种访问方式,因为这会混淆它们之间的重要区别。随意的数据库查询会破坏领域对象的封装和Aggregate。技术基础设施和数据库访问机制的暴露会增加客户的复杂度,并妨碍模型驱动的设计。

客户使用查询方法向Repository请求对象,这些查询方法根据客户所指定的条件(通常是特定属性的值)来挑选对象。Repository检索被请求的对象,并封装数据库查询和元数据映射机制。Repository可以根据客户所要求的各种条件来挑选对象。它们也可以返回汇总信息,如有多少个实例满足查询条件。Repository甚至能返回汇总计算,如所有匹配对象的某个数值属性的总和。

【Repository的实现】对类型进行抽象。Repository "含有"特定类型的所有实例,但这并不意味着每个类都需要有一个Repository. 类型可以是一个层次结构中的抽象超类。类型可以是一个接口。

一般来讲,在使用框架时要顺其自然。当框架无法切合时,要想办法在大方向上保持领域驱动设计的基本原理,而一些不符的细节则不必过分苛求。寻求领域驱动设计概念与框架中的概念之间的相似性。这里的假设是除了使用指定框架之外没有别的选择。

大多数情况下关系数据库是面向对象领域中的持久化存储形式,因此简单的对应关系才是最好的。表中的一行应该包含一个对象,也可能还包含Aggregate的一些附属项。表中的外键应该转换为另一个Entity对象的引用。

七、使用语言:一个扩展的示例

7.1 货物运输系统简介

7.2 隔离领域:引入应用层

7.3 将Entity和Value Object区别开

7.4 设计运输领域中的关联

7.5 Aggregate边界

7.6 选择Repository

7.7 场景走查

7.8 对象的创建

7.9 重构:Cargo Aggregate的另一种设计

7.10 运输模型中的Module

7.11 引入新特性:配额检查