前文回顾
上一篇介绍了该书的第二部分“模型驱动设计的构造块”,将面向对象领域建模中的一些核心的最佳实践提炼为一组基本的构造块。我们了解了用于组织对象集合的AGGREGATE(聚合),创建或重建复杂对象和AGGREGATE的FACTORY(工厂)。
这一篇我们继续学习该书的第二部分。提供查找和对象持久化的REPOSITORY(仓库)。
REPOSITORY(仓库)
在生命周期的中间和末尾,我们使用REPOSITORY(存储库)来提供查找和检索持久化对象,并封装庞大基础设施的手段。为什么要引入REPOSITORY呢?我们先来看看,在对象的生命周期中,无论要用对象执行什么操作,都需要保持一个对它的引用。那么如何获得这个引用呢?
第一种方法是创建对象,因为创建操作将返回对新对象的引用。
第二种方法是遍历关联。我们以一个已知对象 (比如聚合根) 作为起点,并向它请求一个关联的对象。
第三种获取引用的方式——基于对象的属性,执行查询来找到对象;或者是找到对象的组成部分,然后重建它。
当客户端代码直接使用数据库时,开发人员会试图绕过模型的功能(如AGGREGATE,甚至是对象封装),而直接获取和操作他们所需的数据。这将导致越来越多的领域规则被嵌入到查询代码中,或者干脆丢失了。
开发人员可能使用查询从数据库中提取他们所需的数据,或是直接提取具体的对象,而不是通过聚合根来得到这些对象。这样就导致领域逻辑进入查询和客户端代码中,而ENTITY和VALUEOBJECT则变成单纯的数据容器。
有很多成熟的技术可以用来解决数据库访问难题,比如利用METADATA MAPPING LAYER进行对象和表之间的转换。 REPOSITORY是一个简单的概念框架,它可用来封装这些解决方案,并将我们的注意力重新拉回到模型上。
哪些对象需要全局搜索
大多数对象都不应该通过全局搜索来访问。如果很容易就能从设计中看出那些确实需要全局搜索访问的对象,那该有多好!
在所有持久化对象中,有一小部分必须通过基于对象属性的搜索来全局访问。当很难通过遍历方式来访问某些AGGREGATE根的时候,就需要使用这种访问方式。它们通常是ENTITY,有时是具有复杂内部结构的VALUE OBJECT,还可能是枚举VALUE。
而其他对象则不宜使用这种访问方式,因为这会混淆它们之间的重要区别。随意的数据库查询会破坏领域对象的封装和AGGREGATE。
REPOSITORY的参数和返回值
如下图所示,我们可以用REPOSITORY来请求对象,根据条件挑选对象,也可以返回汇总信息,如有多少个实例满足查询条件。REPOSITORY甚至能返回汇总计算,如所有匹配对象的某个数值属性的总和。
为每种需要全局访问的对象类型创建一个对象,这个对象相当于该类型的所有对象在内存中的一个集合的“替身”。它提供添加、删除、挑选对象的方法,从而将实际的存储和查询技术封装起来。应当只为那些确实需要直接访问的AGGREGATE根提供REPOSITORY。
基于SPECIFICATION(规格)的查询是将REPOSITORY通用化的好办法。客户端可以使用规格来描述(也就是指定)它需要什么,而不必关心如何获得结果。如下图所示,在这个过程中,可以创建一个Criteria对象来实际执行筛选操作。
REPOSITORY的实现
理想的实现是向客户端隐藏所有内部工作细节,这样不管数据是存储在对象数据库中,还是存储在关系数据库中,或是简单地保持在内存中,客户端代码都相同。REPOSITORY将会委托相应的基础设施服务来完成工作。将存储、检索和查询机制封装起来是REPOSITORY实现的最基本的特性,如下图所示,REPOSITORY将底层数据存储封装起来。
REPOSITORY的实现方法有很多,我们需要注意以下事项:
- 对类型进行抽象。类型可以是一个层次结构中的抽象超类(例如,TradeOrder可以是BuyOrder或SellOrder)。
- 充分利用与客户端解耦的优点。如果客户端直接调用底层机制,我们就很难修改其实现。
- 将事务的控制权留给客户端。
REPOSITORY与FACTORY的关系
FACTORY负责处理对象生命周期的开始,而REPOSITORY帮助管理生命周期的中间和结束。通过REPOSITORY检索出来的数据通常需要重建为对象形式。 由于在这种情况下REPOSITORY基于数据来创建对象,因此很多人认为REPOSITORY就是FACTORY。
从领域驱动设计的角度来看,FACTORY和REPOSITORY具有完全不同的职责。FACTORY负责制造新对象,而REPOSITORY负责查找已有对象。
如何为关系数据库设计模型
在以面向对象技术为主的软件系统中,最常用的非对象组件就是关系数据库。这种现状产生了混合使用范式的常见问题,在前面的文章中我们提到过这些问题。
从技术上来看,关系表的设计貌似不必反映出领域模型。MODEL DRIVEN DESIGN的关于避免将分析和设计模型分裂的观点,也同样适用于这种问题。保持模型一致确实会牺牲一些对象模型的丰富性,而且有时必须在数据库设计中做出一些折中(如有些地方不能规范化)。但如果不做这些牺牲就会冒另一种风险,那就是模型与实现之间失去了紧密的耦合。
我们不一定非要使用一种简单的、对象和表一对一的映射。依靠映射工具的功能,可以实现一些聚合或对象的组合。但至关重要的是:映射要保持透明,并易于理解。如果有助于简化对象映射的话,不妨牺牲某些关系数据库标准(如规范化)。
当然,可以保持简单的对应关系是最好的。比如表中的一行包含一个对象,也可能还包含AGGREGATE中的一些附属项。
系列文章
- [DDD读书笔记] 运用模型①什么是领域模型
- [DDD读书笔记] 运用模型②通用语言
- [DDD读书笔记] 构造块①分离领域层
- [DDD读书笔记] 构造块②实体、值对象和服务
- [DDD读书笔记] 构造块③模块
- [DDD读书笔记] 构造块④聚合与工厂
- [DDD读书笔记] 构造块⑤仓库
- [DDD读书笔记] 构造块⑥实战模拟
- [DDD读书笔记] 重构①突破
- [DDD读书笔记] 重构②SPECIFICATION模式
- [DDD读书笔记] 重构③柔性设计
- [DDD读书笔记] 重构④使用分析模式和设计模式建模
- [DDD读书笔记] 战略设计①模型上下文策略
- [DDD读书笔记] 战略设计②精炼
- [DDD读书笔记] 战略设计③大型结构