【 第二部分 】模型驱动设计的构造块
第4章:分离领域
模式:LAYERED ARCHTECTURE
想要创建出能够处理复杂任务的程序,需要做到关注点分离——使设计中的每个部分得到单独的关注。在分离的同时,也需要维持系统内部复杂的交互关系。
分层的价值在于每一层都只代表程序中的某一特定方面的。这种限制使每个方面的设计都更具内聚性,更容易理解。
而领域层是模型的精髓。领域模型是一些列概念的集合。“领域层”则是领域模型以及所有与其直接相关的设计元素的表现,他由业务逻辑的设计和实现组成。在MODEL-DRIVEN-DESIGN中,领域层的软件构造反映出了模型概念。
【学习心得】:分离意味着原始的复杂,这是发展的一个趋势,技术的进步往往在于精细化的分工,而这种分层的另一个好处是,分离核心,聚焦问题。
第5章:软件中所表示的模型
模式:ENTITY(又称为REFERENCE OBJECT)
一些对象主要不是由它们的属性定义的。它们实际上表示了一条“标识线”(A Thread of Identity),这条线跨越时间,而且常常经历多种不同的表示。有时,这样的对象必须与另一个具有不同属性的对象相匹配。而有时一个对象必须与具有相同属性的另一个对象区分开。错误的标识可能会破坏数据。
当一个对象由其标识(而不是属性)区分时,那么在模型中应该主要通过标识来确定该对象的定义。是类定义变得简单,并集中关注生命周期的连续性和标识。定义一种区分每个对象的方式,这种方式应该与其形式和历史无关。要格外注意那些需要通过属性来匹配对象的需求。在定义标识操作时,要确保这种操作为每个对象生成唯一的结果,这可以通过附加一个保证唯一性的符号来实现。这种定义标识的方法可能来自外部,也可能是由系统创建的任意标示符,但它在模型中必须是唯一的标识。模型必须定义“符合什么条件才算是相同的事物”。
模式:VALUE OBJECT
很多对象没有概念上的标识,它们描述了一个事务的某种特征。而这类用于描述领域的某个方面而本身没有概念标识的对象称为VALUE OBJECT(值对象)。
当我们只关心一个模型元素的属性时,应把它归类为VALUE OBJECT。我们应该使这个模型元素能够表示出其属性的意义,并为它提供相关功能。VALUE OBJECT应该是不可变的。不要为它分配任何标识,而且不要把它设计成像ENTITY那么复杂。
模式:SERVICE
有时,对象不是一个事物。在某些情况下,最清楚、最实用的设计会包含一些特殊的操作,这些操作从概念上讲不属于任何对象。与其把它们强制地归于哪一类,不如顺其自然地在模型中引入一种新的元素,这就是SERVICE(服务)。
所谓SERVICE,它强调的是与其他对象的关系。与ENTITY和VALUE OBJECT不同,它只是定义了能够为客户做什么。SERVICE往往是一个一活动来命名,而不是以一个ENTITY来命名,也就是说,它是动词而不是名词。
好的SERVICE有以下3个特征:
(1)与领域概念相关的操作不是ENTITY或VALUE OBJECT的一个自然组成部分。
(2)接口是根据领域模型的其他元素定义的。
(3)操作是无状态的。
当领域中的某个重要的过程或转换操作不是ENTITY或VALUE OBJECT的自然职责时,应该在模型中添加一个作为独立接口的操作,并将其声明为SERVICE。定义接口时要使用模型语言,并确保操作名称是UBIQUITOUS LANGUAGE中的术语。此外,应该使SERVICE成为无状态的。
SERVICE与孤立的领域层:这种模式只重视那些在领域中具有重要意义的SERVICE,但SERVICE并不只是在领域中使用。我们需要注意区分属于领域层的SERVICE和那些属于其他层的SERVICE,并划分责任,以便将它们明确地区分开。
模式:MODULE(也称为PACKAGE)
MODULE是一个传统的、较成熟的设计元素。虽然使用模块有一些技术上的原因,但主要原因却是“认知超载”。MODULE为人们提供了两种观察模型的方式,一是可以在MODULE中查看细节,而不会被整个模型淹没,二是观察MODULE之间的关系,而不考虑其内部细节。
每个人都会使用MODULE,但却很少有人它们当作模型中的一个成熟的组成部分。代码按照各种各样的类别进行分解,有时是按照技术架构来分割的,有时是按照开发人员的任务分工来分割的。甚至那些从事大量重构工作的开发人员也倾向于使用项目早期形成的一些MODULE。
众所周知,MODULE之间应该是低耦合的,而在MODULE的内部则是高内聚的。耦合和内聚的解释使得MODULE听上去像是一种技术指标,仿佛根据关联和交互的分布情况来机械地判断它们。然而,MODULE并不仅仅是代码的划分,而且也是概念的划分。一个人一次考虑的事情是有限的(因此才要低耦合)。不连贯的思想和“一锅粥”似的思想同样难于理解(因此才要高内聚)。
因此:选择能够描述系统的MODULE,并使之包含一个内聚的概念集合。这通常会实现MODULE之间的低耦合,但如果效果不理想,则应寻找一种更改模型的方式来消除概念之间的耦合,或者找到一个可以作为MODULE基础的概念(这个概念先前可能被忽视了),基于这个概念组织的MODULE可以以一种有意义的方式将元素集中到一起。找到一种低耦合的概念组织方式,从而可以相互独立地理解和分析这些概念。对模型进行精化,直到可以根据高层领域概念对模型进行划分,同时相应的代码也不会产生耦合。MODULE的名称应该是UBIQUITOUS LANGUAGE中的术语。MODULE及其名称应反映出领域的深层知识。
【学习心得】:每一个概念或方法,都有其含义来源和出处。学会寻找信息的源头,学会给自己的认知指明来源和出处,具备严谨的逻辑思维,科学地学习和认知,是一切成功的基础。杜绝垃圾二手信息资料,杜绝自我局限性拍脑袋的认知决策过程。
第6章:领域对象的什么周期
模式:AGGREGATE
在具有复杂关联的模型中,要想保证对象更改的一致性是很困难的。不仅互不关联的对象需要遵守一些固定规则,而且紧密关联的各组对象也要遵守一些固定规则。然而,过于谨慎的锁定机制又会导致多个用户之间毫无意义地互相干扰,从而使系统不可用。
固定规则(invariant)是指在数据变化时必须保持一致性规则,其涉及AGGREGATE成员之间的内部关系。而任何跨越AGGREGATE的规则将不要求每时每刻都保持最新状态。通过事件处理,批处理或其他更新机制,这些依赖会在一定时间内得以解决。但在每个事务完成时,AGGREGATE内部所应用的固定规则必须得到满足。为了实现这个概念上的AGGREGATE,需要对所有事务应用一组规则:
□ 根ENTITY具有全局标识,它最终负责检查固定规则。
□ 根ENTITY具有全局标识。边界内的ENTITY具有本地标识,这些标识只在AGGREGATE内部才是唯一的。
□ AGGREGATE外部的对象不能引用除根ENTITY之外的任何内部对象。根ENTITY可以把对内部ENTITY的引用传递给它们,但这些对象只能临时使用这些引用,而不能保持引用。根可以把一个VALUE OBJECT的副本传递给另外一个对象,而不必关心它发生什么变化,因为它只是一个VALUE,不再与AGGREGATE有任何关联。
□ 作为上一条规则的推论,只有AGGREGATE的根才能直接通过数据库查询获取。所有其他对象必须通过遍历关联来发现。
□ AGGREGATE内部的对象可以保持对其他AGGREGATE根的引用。
□ 删除操作必须一次删除AGGREGATE边界之内的所有对象。(利用垃圾回收机制,这很容易做到。由于除了根以外的其他对象都没有外部引用,因此删除了根以后,其他对象均会被回收。)
□ 当提交对AGGREGATE边界内部的任何对象的修改时,整个AGGREGATE的所有固定规则都必须满足。
我们应该将ENTITY和VALUE OBJECT分门类别地聚集到AGGREGATE中,并定义每个AGGREGATE的边界。在每个AGGREGATE中,选择一个ENTITY作为根,并通过根来控制对边界内其他对象的所有访问。只允许外部对象保持对根对象的引用。对内部成员的临时引用可以被传递出去,但仅在一次操作中有效。由于根控制访问,因此不能绕过它来修改内部对象。这种设计有利于确保AGGREAGATE中的对象满足所有固定规则,也可以确保在任何状态变化时AGGREGATE作为一个整体满足固定规则。
模式:FACTORY
当创建一个对象或创建整个AGGREGATE时,如果创建工作很复杂,或者暴露了过多的内部结果,则可以使用FACTORY进行封装。
对象的创建本身可以是一个主要操作,但被创建的对象并不适合承担复杂的装配操作。将这些职责混在一起可能产生难以理解的拙劣设计。让客户直接负责创建对象又会使客户的设计陷入混乱,并且破坏被装配对象或AGGREGATE的封装,而且导致客户与被创建对象的实现之间产生过于紧密的耦合。
因此:应该讲创建复杂对象的实例和AGGREGATE的职责转移给单独的对象,这个对象本身可能没有承担领域模型中的职责,但它仍是领域设计的一部分。提供一个封装所有复杂装配操作的接口,而且这个接口不需要客户引用要被实例化的对象的具体类。在创建AGGREGATE时要把它作为一个整体,并确保它满足固定规则。
模式:REPOSITORY
在所有持久化对象中,有一小部分必须通过基于对象属性的搜索来全局访问。当很难通过遍历方式来访问某些AGGREGATE根的时候,就需要使用这种访问方式。它们通常是ENTITY,有时是具有复杂内部结构的VALUE OBJECT,还可能是枚举VALUE。而其他对象则不宜使用这种方式,因为这会混淆它们之间的重要区别。随意的数据库查询会破坏领域对象的封装和AGGREGATE。技术基础设施和数据库访问机制的暴露会增加客户的复杂度,并妨碍模型驱动的设计。
REPOSITORY是一个简单的概念框架,它可用来封装这些解决方案,并将我们的注意力重新拉回到模型上。REPOSITORY将某种类型的所有对象表示为一个概念集合(通常是模拟的)。它的行为类似于集合(collection),只是具有更复杂的查询功能。
因此:为每种需要全局访问的对象类型创建一个对象,这个对象相当于该类型的所有对象在内存中的一个集合的“替身”。通过一个众所周知的全局接口来提供访问。提供添加和删除对象的方法,用这些方法来封装在数据存储中实际插入或删除数据的操作。提供根据具体条件来挑选对象的方法,并返回属性值满足查询条件的对象或对象集合(所返回的对象是完全实例化的),从而将实际的存储和查询技术封装起来。只为那些确实需要直接访问的AGGREGATE根提供REPOSITORY。让客户始终聚焦于模型,而将所有对象的存储和访问操作交给REPOSITORY来完成。
FACTORY与REPOSITORY的关系是:FACTORY负责处理对象生命周期的开始,而REPOSITORY帮助管理生命周期的中间和结束。从领域驱动设计的角度来看,FACTORY和REPOSITORY具有完全不同的职责。FACTORY负责制造新对象,而REPOSITORY负责查找已有对象。
【学习心得】:有时候学习上的困难不是因为自己的理解能力差,而是缺乏一定的基础沟通语言。急于求成和半路出家的问题就在于基础的不扎实,也就是我们所说的野路子。我曾经也会认为用到了再来学,这都是技术圈子的一个悖论。就好像等自己需要用钱了再来理财一样可笑。
第7章:使用语言:一个扩展的示例
略。