《领域驱动设计》读书笔记(4)——领域对象的生命周期

第六章 领域对象的生命周期

在程序运行的过程中,大部分的对象都是在被创建出来之后很快就被垃圾回收了,但是领域对象,如实体和值对象往往具有复杂的生命周期。它们不仅被创建回收,还会被持久化,从持久化设备中重建等。而且这些对象之间并不是孤立的,它们之间存在着天然的关联,这些对象之间要在生命周期的每一个时刻都满足一些不变量。对象之间的关联,以及它们之间的不变量来源于领域,比如说订单就有很多订单明细,而订单的总金额就是明细的金额之和。如何管理这些对象给我们提出了挑战,处理不好就很容易使我们的工作偏离模型驱动的方向。挑战可以分为两类:
  • 在生命周期中维护对象的完整性
  • 避免模型由于管理生命周期的复杂性而陷入困境
本章介绍了三种模式用来解决这个问题。

6.1 聚合

对象之间的关联越少,维护对象的代价就越低,所以在设计中应该尽量减少对象之间的关联。然而,现实的领域对象往往都会有着复杂的关联,因为领域本身就是这样的。 在包含着复杂关联的模型中,要保证对象修改的一致性是很困难的。我们必须保证紧密关联的对象组也能保持不变性,而不仅仅是保证各个离散的对象。如果锁定策略过于谨慎,就会导致多个用户毫无必要的相互干扰,使系统变得无法使用。 我们需要一种抽象机制用于在模型中对引用进行封装。一个聚合是一簇相关联的对象,出于数据变化的目的,我们将这些对象视为一个单元。每个聚合都有一个根和一个边界。边界定义了根中包含什么;根是包含在聚合中的单个特定的实体。根是聚合中惟一允许被外部对象引用的元素,但在聚合的边界内,对象之间可以互相引用。根之外的实体具有本地标识,但是它们仅仅在聚合内部才需要区分其标识,因为根实体上下文以外的对象不会看到它们。 不变量是指无论何时数据发生变化都必须满足的一致性原则。聚合的成员之间可能存在着不变量关系。我们不能指望涉及到聚合的所有规则是每时每刻都被满足的,一些依赖关系只能在某些特定的时刻,通过事件处理、批处理或其他更新机制来解决。但是,聚合内部的不变量必须是在每次事务完成时就被满足的。 现在,为了将概念上的聚合转换为实现,我们需要在所有的事务中应用一系列的规则。
  • 根实体具有全局标识,并最终负责对不变量的检查。
  • 根实体具有全局标识。边界内的实体具有本地标识,这些标识仅在聚合内部是惟一的。
  • 聚合边界以外的任何对象除了可以引用根实体,不能持有任何对其内部对象的引用。根实体可以把内部实体的引用传递给其它对象,但是它们只能临时使用这个引用而不能持有这种引用。根还可以复制一个值对象的复本给另一个对象。它并不关心这个副本会发生什么变化,因应那只是一个值,而且与聚合已经不再有任何的关联了。
  • 作为上一条规则的推论,能通过数据库查询直接获得的对象只有聚合根。所有其它对象必须通过导航来访问。
  • 聚合内的对象可以持有其它聚合根的引用。
  • 删除操作必须一次性删除聚合边界内的所有对象。
  • 当在聚合边界内发生的任何对象修改被提交时,整个聚合的所有不变量必须都被满足。
将实体和值对象聚集到聚合中。第个聚合定义一个边界。为每个聚合选择一个实体作为其根,并通过根来控制所有对边界内对象的访问。外部对象只能持有对象根的引用;对内部对象的临时引用只能在单个操作中使用。由于根控制了访问,因此我们无法绕过它去修改内部元素。这种安排使得我们可以保证在任何状态变化中,聚合本身(作为整体)的不变量,以及聚合中对象的不变量都可以被满足。 聚合是一个范围比对象更大,比模块更小的封装的概念。分析领域可以得到领域对象之间应该满足的哪些规则和不变量,但是却不能直接告诉我们有哪些聚合。设计聚合不仅要看哪些对象之间有哪些不变量,还要看在实现上,将哪些对象划分在一个聚合中更易于实现。上面列出的种种实现上的约束是实现聚合的具体方法,如果不满足那些约束,那么聚合的封装将被打破,聚合也就失去了意义。

6.2 工厂

某些对象或聚合的创建过程非常复杂,将这一创建职责交给这些对象中任何一个都不合理,将这一职责交给客户会导致领域逻辑的外泄。因此要在领域层引入一种新的对象,工厂,由它专门负责复杂的创建职责。 任何好的工厂都必须满足两个基本的要求:
  • 每个创建方法都是原子的,并保证所创建的对象或聚合能保证所有的不变量。工厂所创建出来的对象在状态上必须是一致的。对于实体来说这意味着它创建的是一个完整的、满足所有不变量的聚合,但有一些可选的元素可能还有待加入。对于有不变性的值对象来说,这意味着其所有的属性都初始化到为正确的最终状态。有时工厂可能无法根据外部请求把对象正确的构造出来,如果工厂的接口允许这种请求,那么它应该抛出一个异常,或者运用其它机制来保证不会返回错误的对象。
  • 工厂应该将构造结果抽象到所需的类型,而不是它所创建的具体的类型。
不是所有时候都要使用工厂,在以下情况我们倾向于使用原始的公有构造函数:
  • 类就是类型。它不是任何层次结构的一部分,也没有通过实现接口来实现多态;
  • 客户关心具体的实现,可能作为选择策略的一种方法;
  • 客户可以使用对象的所有属性,因此提供给客户的构造函数中没有嵌入对象的任何创建逻辑;
  • 构造函数并不复杂;
  • 公有构造函数必须遵循和工厂一样的规则:它必须是一个原子操作,并保证创建出来的对象满足所有不变量。
可见往往不需要为每个实体和值对象做一个工厂,因为不总是所有的实体和值对象的创建都是那么复杂,都要封装实现,况且,聚合应当作为一个整体被创建。 工厂不仅可以用来创建一个不存在的对象,还可以用来把持久化或序列化的已有对象重建。此时要注意两点:
  • 重建时要使用已有的实体的标识。
  • 如果已有数据有误,可以重建的对象不能满足不变量,此时应当根据情况采取灵活的应对措施。

6.3 仓储

仓储用来查找到已有的领域对象,保存新的领域对象,保存已有领域对象的变更。业务过程对于计算机来说往往是漫长的,常常需要将领域对象持久化,再读取,更新,再持久化。这些持久化操作本身并不是领域的一部分,为了避免持久化逻辑污染领域模型,必须将持久化逻辑封装起来,仓储就是用来做这件事的。 客户需要一种可行的方法来获得已有的领域对象的引用。如果基础结构允许客户的开发人员很容易地加入关联,那么他们就加入更多的导航关联,把模型弄得一团糟。另外一种可能是,开发人员会利用查询提取他们需要的额外的数据,或者是一些特殊和对象,而它们本来是应该通过聚合根来访问的。领域逻辑跑到查询代码和客户代码中去了,而实体和值对象变成了纯粹的数据容器。大部分数据库访问基础结构的技术复杂性,很快使得客户端代码陷入混乱,最终开发人员只好抛开领域层,把模型变成一个摆设。 部分持久对象必须通过按对象属性进行查询的方式来实现全局访问。对于不便于对过导航来访问的聚合根来说,这种访问方式是必须的。这些对象通常是实体,有时是包含复杂数据结构的值对象,有时是枚举值。为其它对象提供这种访问会使一些重要的区别变得模糊。不受限制的数据库查询实际上会破坏领域对象和聚合的封装。把技术基础结构和数据库访问机制暴露出来,会使客户变得复杂,同时掩盖了模型驱动设计。 为每种需要全局访问的对象类型创建一个对象,该对象为该类型所有对象在内存中的集合提供影像。用一个众所周知的全局接口来设立访问入口。提供增删对象的方法,把对数据存储的实际的插入和删除封装起来。提供根据某种标准筛选对象的方法,返回完整实例化了的属性值符合标准的对象或对象集合,把实现的存储和查询技术封装起来。仅为确实需要直接访问的聚合根提供仓储。让客户聚焦于模型,把所有的对象存储和访问的工作委托给仓储来完成。 尽管应当用仓储来封装所有的数据访问,但是客户程序员还是需要了解仓储的实现机制,以避免出现意料外的性能问题。 实现仓储的时候可以选择一个良好的持久层框架,提高开发效率,但要清楚考虑到这些框架对仓储的限制,应当尽量不要让领域层被框架所污染。 工厂和仓储都不是领域中已有的概念,但是工厂里包含全是领域逻辑,而没有数据访问逻辑,而仓储则全是数据访问逻辑而尽量不要包含领域逻辑。我们可以让工厂创建一个对象,让仓储持久化这个对象,仓储也可以读取已有领域对象的持久化数据后,让工厂重建领域对象。 将对象持久化到关系型数据库是一大挑战,有时为了实现上的考虑,不得不做一些打破数据范式的折衷,但是这些数据实现上的细节应当被仓储给封装起来,不要让领域层依赖这些细节。比如,由于遗留数据的原因,有时不得不将一些领域对象存储到不同结构的不同的数据库中;再比如,为了改进性能,有时需要增加一些冗余这段。这些都应当被仓储给封装起来。

转载于:https://my.oschina.net/komodo/blog/919180

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值