`
haierboos
  • 浏览: 434521 次
文章分类
社区版块
存档分类
最新评论

基于数据访问的集合类型-领域驱动设计的又一种特定对象

 
阅读更多

  企业级应用的一个显著特点是需要处理大量的数据,开发人员应该小心地从数据库中加载对象,确保只加载了供业务处理使用的必须的最小限度的对象集合。但真正做到这一点是很难的。其中一个原因就是:现实世界中,事物很少孤立存在,它们之间往往有着这样或是那样的联系,体现到对象模型上就是对象间的依赖。由一个对象展开,可能会依赖N个对象,N个对象又可能会依赖N*M个对象。很快,数量巨大的对象就被牵扯进来。由于在对象加载时刻根本无法预知哪些依赖的对象会被访问到,这就为最小化加载对象带来了不确定性。我们来看一个简单的例子:
  在一个论坛系统中,一个Forum(论坛)会包含多个Thread(主题),Forum与Thread之间是典型的一对多的父子关系.从领域模型的角度考虑,让Forum拥有一个Thread的集合是最直接也是最自然的实现方案.但是考虑到Thread的数据可能非常庞大,我们不可能在加载Forum时把它所有的Thread全部加载出来,而如果通过Repository进行即时查询则会导致领域对象对Repository的依赖.这在领域驱动设计中是一个非常典型的问题.目前人们通常的处理方法有两种:
1.不在意领域对象对Repository依赖,通过向领域对象注入Repository进行动态查询.
2.使用DomainEvent模式以事件机制与Repository通信获得所需的数据(http://www.udidahan.com/2009/06/14/domain-events-salvation/).
第一种方案并不值得推荐.而第二种方案虽然避免了领域对象对Repository的直接依赖,但是它的实现很不自然,"被设计"的成分过重,人们并不能从那些领域对象的业务方法中很直接地了解到为什么会触发这样或那样的事件,以及这些事件被如何处理了。因为这些领域事件本质上是应设计上的需要而建立的,并非是领域中真实发生的事件。
  让我们回到问题原点:从领域模型上看,Forum拥有一个Thread集合(这里的“集合”是一般意义上的集合,与程序无关),这是领域的一个事实。那么在实现模型的设计上,直接将Forum的Thread集合映射为Forum实体的一个集合类型(这里的“集合”特指编程语言中集合类)的字段就是一种最自然的映射,我们的问题是要控制集合内元素的加载,既要确保集合内的元素不会一次性全部加载出来,又要保证Client在访问集合内的某些元素时,它们能够被及时地加载出来。联想Repository在领域中扮演的角色和具体的实现,我们不难想到,我们的集合也可以仿照Repository来实现,这样它将满足以下两点:
1.这个集合在名义上代表着一组满足特定条件的领域对象,并且可以像普通集合类那样使用(即应提供集体类型基本API)。
2.这个集合的内部并不真的包含这些领域对象,而是在需要时从数据库中动态的加载出来。
这就是我要提出的针对上述问题的一种更自然更贴近领域的解决方案:基于数据访问的集合类型.


在通过Repository查询一个Forum包含那些thread并获取一部分子集的过程中,我们可考虑Repository和Forum,以及Forum的threads集合之间的关系:从领域模型的角度来看,Repository是一种Artifact,与领域问题无关,Forum是不需要的Repository的,而是应该有一个持有一个像List<Thread>这样的集合。但是我们显然不能这样做。那么这个时候Reppository要参与进来,帮助Forum得到它的Thread.

基于数据访问的集合类-领域驱动设计的又一种特定对象;

对于Forum的getLastPost方法,是一个值得好好考虑的问题。假如是在一个非基于DB的环境里,Forum找到最后一个发表的贴子的方法是遍历它的所有帖子,找到发表时间最晚的一个,我们认为这过程是一个“领域问题”求解的过程,也就是领域逻辑的体现。那么对于基于数据库的系统来说,由于数据量巨大,为了找到最后发表的帖子而把Forum0所有的帖子加载出来是非常愚蠢的,我们只能以Sql的形式完成查找,这样,关于找到的这部分领域逻辑就跑到Sql里去了,这看起来真得是一个很难调和的问题!至少我们现在有一种选择:即通过DDD中的

Specification之类的模式,以声明性的设计把领域逻辑保留在了业务模型中!

在一些领域对象中,总会遇到这样一些方法,他们需要从大量的对象中打到他们所需的对象,依赖它们完成业务操作,像getLastPost就是这样。也就是说:领域对象的业务方法会不可避免需要查询数据,为了保证领域对象的纯净,我们自然不会把数据查询的操作嵌入到业务方法中,我们需要更加自然或是透明的方法

事实上,这种Collection在形式上非常接近传统的集合类型,但其具体的实现是基于数据访问的.我们可以把这种集合视为一种传统集合与数据访问对象间的适配器.通过引入这种Collection,可以避免领域对象对Repository的依赖,而且领域对象依赖的只一个表征某种数据集合,并不暴露任何数据访问细节的Colection接口,这使得我们的领域对象更加"纯净",有助于将领域对象聚焦在领域逻辑上.并且,这种集合使用起来也非常地自然.

关于Collection接口的设计:
由于Collection实现是基于数据访问的,因些,有些传统集合方法并不适用于这种集合,而它也有一般集合不会有的方法.这些与它们需要访问数据库并且可能处理大量数据的特性有关.比如

与Repository的对比:
Collection与Repository有很多类似的地方.对于Repository来说,它总是有两方面的意义,从领域角度来看,Repository代表着某类领域对象的全部集合,我们可以从Repository中找到我们需要的对象,或者通过它来添加删除或更新对象,Repository在领域的实现模型(这里,我只能严谨地说是在领域的实现模型中而非领域模型)中确实是一个必要的角色.从它的具体实现来看,它实际上是一个数据访问对象(或者依赖于数据访问对象),它封装了数据访问的细节.
Collection同样具有这两方面的意义,从领域角度来看,它代表了某类领域对象的某种特定的子集,而其具体的实现也同样是对数据访问的封装.
如果说我们把ThreadRepository看成一个存储了全部Thread的集合,那么,Forum从中找到属于它的那些Thread也是可以理解的。但是这样有些意味反生了微妙的变化,即,对于Forum来说,它本身应该是一小撮Thread的集合,而这一点,在基于Repository的实现中被偷换了。

比如:我们现在需要一个对象A,但是A会依赖于对象B,那我们是否要在加载A时把B一起加载出来呢?很多时候,答案是不确定的。因为在加载A的时刻,我们没有足够信息来判断是否应该加载B,更遭的是,B有可能会依赖C,D,E...,这会使问题使变得更加复杂。一种好的解决方案就是Lazy Loading。很多ORM框架都大量采用了这种模式。但是限于



因此对于对象的加载这里有一个假定的背景:即所需对象。
,在处理对象的创建如果使用DDD,一个必须面对的问题是:如果加载数据。你不可能像在其他类型的系统中那样轻易地加载任何数据。


这样集合有点类似于一个小Repositry,或者说是Repository的子集,但更倾向于做为“集合”的形式存在,因而应该包含像size(),get(int index),remove(),add(),这样的方法。它的存在避免了领域对象对Repository的依赖。使得领域对象更加“纯”,有助于聚焦于领域问题。


基于数据访问的集合类型--一种传统集合与Repository之间适配器在DDD中应用.
如果说Repository代表某种对象的全部集合,那么 基于数据访问的集合就可以视作满足某种条件的这种对象的特定子集.

在领域驱动设计中,Repository负责聚合根的全局读写,而聚合内的其他对象则通过由聚合根开始的导航来访问.聚合为领域对象划分出一个清晰的边界.揭示了领域对象在读写一致性上的深层次关联.

聚合与Repository在理论上是完美的,但是实际应用中,开发人员会常常遇到这样一种尴尬的问题,即:当聚合根与某非聚合根对象是一对多的关系,并且非聚合根对象的数量非常庞大时,通过导航访问这些非聚合根对象可能会引发严重的性能问题.


没有针对单一Message的全局追踪与访问,所有的Message总是由它从属的Thread得到,那么这时,Thread和Message就组成了一个聚合,Thread作为聚合根,与Message之间是典型的一对多的父子关系.在基于Hibernate的实现中,它们的典型配置是:


领域驱动设计基于面向对象技术解决复杂的领域问题,数据驻留在对象内,对数据的访问体现为对象间的导航。

我对DomainEvent模式也有过研究,但是我一直认为这并不是一个完美的方案。诚然,DomainEvent使得领域对象和Application Service与Repository之间解耦,但却引入对第三方DonmainEvent的依赖,而DomainEvent在本质上与Application Service和Repository一样,并非是领域的一部分,而是应设计上的需要才建立的,人们并不能从那些领域对象的业务方法中很直接地了解到为什么会触发这样或那样的Domain Event,以及这些Domain Event被如何处理了。

分享到:
评论

相关推荐

    领域驱动设计与模式实战

    1.3.2 领域驱动设计 1.3.3 测试驱动开发 1.3.4 重构 1.3.5 选择一种还是选择组合 1.4 持续集成 1.4.1 解决方案(或至少是正确方向上的一大步) 1.4.2 从我的组织汲取的教训 1.4.3 更多信息 1.5 不要忘记运行机制 ...

    超级有影响力霸气的Java面试题大全文档

    exception 表示一种设计或实现问题。也就是说,它表示如果程序运行正常,从不会发生的情况。 19、同步和异步有何异同,在什么情况下分别使用他们?举例说明。  如果数据将在线程间共享。例如正在写的数据以后可能...

    java 面试题 总结

    对于客户机,EntityBean是一种持久性对象,它代表一个存储在持久性存储器中的实体的对象视图,或是一个由现有企业应用程序实现的实体。 Session Bean 还可以再细分为 Stateful Session Bean 与 Stateless Session ...

    Python语言程序设计习题答案.zip

    组合数据类型是Python语言区别于其他高级编程语言的一大特色,通过组合数据类型,省去了其他语言各种复杂数据结构的设计,给编程人员带来了极大的方便,这也是Python流行于数据分析领域的原因之一。学习本章,要熟练...

    Python语言程序设计PPT课件.zip

    组合数据类型是Python语言区别于其他高级编程语言的一大特色,通过组合数据类型,省去了其他语言各种复杂数据结构的设计,给编程人员带来了极大的方便,这也是Python流行于数据分析领域的原因之一。学习本章,要熟练...

    java开源包11

    提供了一个基于对象模型的 ActionScript 字节码,并提供了 ActionScript 字节码统计工具。 Java类重加载工具 JReloader JReloader 是一个用来重新加载class文件而无需重启JVM的工具。 PHPJava Bridge php调用java...

    java开源包4

    提供了一个基于对象模型的 ActionScript 字节码,并提供了 ActionScript 字节码统计工具。 Java类重加载工具 JReloader JReloader 是一个用来重新加载class文件而无需重启JVM的工具。 PHPJava Bridge php调用java...

    GoodProject Maven Webapp.zip

    微软推荐的分层式结构一般分为三层,从下至上分别为:数据访问层、业务逻辑层(又或称为领域层)、表示层。 1:数据访问层:主要是对非原始数据(数据库或者文本文件等存放数据的形式)的操作层,而不是指原始数据,也...

    XML高级编程

    因为我们的样式是由样式单驱动而不是嵌入在数据当中的,所以我们能够专门针对无线设备创建一种有效的显示格式。总而言之,我们的显示主线包括:第8章:链接和查询第9章:操作XML 第13章:样式化第14章:WAP 阅读本书...

    ROS机器人操作系统入门 Tutorials CN版 20150726

    ROS的运行架构是一种使用ROS通信模块实现模块间P2P的松耦合的网络连接的处理架构,它执行若干种类型的通讯,包括基于服务的同步RPC(远程过程调用)通讯、基于Topic的异步数据流通讯,还有参数服务器上的数据存储。...

    会计理论考试题

    13.微机的键盘是一种分离式的智能键盘,通过电缆与主机连接。( Y ) 14.计算机的常用输出设备有打印机和键盘。( N) 15.汉字语音识别输入技术属于汉字智能输入技术。( Y ) 16.硬盘安装在主机箱内,一般用符号C:...

    计算机要学哪些东西----(还有附赠哦)

    每个领域由一个两个字母的缩写词表示,比如OS代表操作系统,PL代表程序设计语言,领域之下又被分割成更小的单元(units),代表领域中单独的主题模块。每个单元都用一个领域名加一个数字后缀表示,比如OS3是关于并发...

Global site tag (gtag.js) - Google Analytics