Repository 仓储,你的归宿究竟在哪?(一)-仓储的概念

写在前面

写这篇博文的灵感来自《如何开始DDD(完)》,很感谢young.han兄这几天的坚持,陆陆续续写了几篇有关于领域驱动设计的博文,让园中再次刮了一阵“DDD探讨风”,我现在不像前段时间那样“疯狂”了,写博文需要灵感,就像这篇一样。那篇博文除去其他的一些问题探讨,留给我印象最深的就是:领域服务中使用仓储,下面摘自文中我的一段评论:

  1. 领域服务中去调用仓储,这一点是我一直所纠结的地方,我现在做的项目是领域服务中是不参杂着仓储的,这个操作是在应用层中,比如:_userRepository.Add(user);
  2. 兄台接下来说的观点,首先要明确一点的是:仓储应不应该在领域服务中进行调用???我上次写了那篇文章,其实到最后也没讨论出个结果,反正我现在所做的是,领域服务不实现仓储调用。你可以结合测试驱动开发就知道没什么了,DDD+TDD,其实领域模型最好的业务体现是在哪?不是在领域模型,而是领域模型的单元测试,它是很好的描述这个业务用例,如果你的领域模型的单元测试出了问题,那就是领域模型出了问题,其实兄台可以试着写下你这个业务场景下的领域模型的单元测试,也就是一个业务用例的单元测试,看看会发生什么?还有就是应用层的伪代码。

文中Luminji兄这样回复我:“领域服务不用仓储,那我们怎么单元测试领域服务?仅此一点,就说明领域服务必用仓储。反之,倒是上层,如控制器这里不应该用仓储。”其实原本大家的焦点不应该放在仓储上面的,而应该放在领域驱动设计的核心-领域模型上,为此我还曾写了几篇关于领域模型设计的博文,但是一个完整的应用程序不只是包含领域模型,还有其他的东西需要进行探讨,虽然它不像领域模型那么重要,但同样必不可少。

Luminji兄的评论,让我意识到需要把领域驱动设计中的其他概念明确探讨下了,如果对一些概念模糊不清,或者不能很好的明确其职责,这样就很容易导致我们在领域驱动设计的过程中陷入一些困境,就像我之前所掉进的坑-《设计窘境:来自 Repository 的一丝线索,Domain Model 再重新设计》。

以下内容只是个人对仓储概念及其问题进行探讨,并非是结论总结,仅供各位仁兄参考。

《实现领域驱动设计》

在进行正文探讨之前,我先啰嗦几句。

《实现领域驱动设计》这本书,我在之前觉得没必要阅读,因为当时认为学习领域驱动设计,只要精读下 Eric Evans 的经典著作《领域驱动设计-软件核心复杂性应对之道》就可以了,但是DDD是需要进行实践的,Eric Evans 只是提出领域驱动设计这个概念,有关于其实现,书中并没有花很大的精力去讲解,而《实现领域驱动设计》这本书正是弥补了这一点。

这两本书的阅读顺序,当然是先阅读《领域驱动设计》,然后再阅读《实现领域驱动设计》,如果你是第一次读第一本书,它会颠覆你对软件设计的一些看法,然后让你不能自拔的“爱上它”,不知道你有没有,反正我是这样,然后你在做一些应用程序设计的时候,会尝试使用领域驱动设计,虽然有些步履蹒跚,但是走出第一步是很重要的。关于阅读第二本书,我的建议是,在阅读之前,先根据第一本书中的指导,自己尝试去实践领域驱动设计,最好是做一些实际业务场景的应用,在这个过程中,完全按照自己对领域驱动设计的想法去实现,虽然可能会掉进一些深坑,但是我觉得只有这样你才会理解的更加深刻。至于为什么自己实践过领域驱动设计再去阅读第二本书?因为实践过后阅读的话,你会与作者产生一些共鸣,这是很奇妙的感觉,就像译者-腾云这样所说:

《实现领域驱动设计》这本书,我现在也只读了第十二章-资源库(译者把 Repository 翻译为资源库,和仓储是一个意思,我更喜欢仓储这个名词,后面就用它来表示 Repository 了),阅读仓储这一章的时候,我是带着问题进行阅读的,也就是仓储的职责是什么?它的归宿究竟在哪?但是很可惜,我在这一章节中并没有找到我要寻找的答案,因为作者主要讲解的是仓储的实现,但是我发现了其他一些有意思的东西,下面希望和各位仁兄分享下(或许有点偏离主题了,但是我觉得应该会蛮有意义的)。

仓储(Repository) VS 数据访问对象(DAO)

有关于仓储的概念,我不止在一篇博文中进行说明,但是这边既然和数据访问对象进行比较的话,还是要声明一下,下面来自《领域驱动设计》书中的定义:

Repository(仓储):协调领域和数据映射层,利用类似与集合的接口来访问领域对象。

也可以像 dudu 这样进行直白的理解:Repository 是一个独立的层,介于领域层与数据映射层(数据访问层)之间。它的存在让领域层感觉不到数据访问层的存在,它提供一个类似集合的接口提供给领域层进行领域对象的访问。Repository 是仓库管理员,领域层需要什么东西只需告诉仓库管理员,由仓库管理员把东西拿给它,并不需要知道东西实际放在哪。

仓储是领域驱动设计中产生的概念,也就是说,如果你的应用程序不是基于领域驱动设计的,那在设计中使用仓储是不是有点不伦不类呢?首先,就像 Eric Evans 所定义中明确的那样:协调领域和数据映射层,两个关键字领域数据映射层,这里面的领域是指领域模型(实体和值对象),这是桥的一头,另一头就是数据映射层,也就是我们常说的 ORM 工具,在 .NET 领域也就是我们常用的 EntityFramework,很多人认为 EntityFramework 就包含仓储,好像之前有人发表过博文阐述过这个问题,但是你看下仓储的定义,就会发现这不是一个概念的问题。除了这两个关键词,还有一个动词就是协调,仓储协调的是什么?怎么协调的?这个概念需要明确下,桥的一头-领域模型(主要是实体对象),这个就不多说了,桥的另一头-ORM(对象关系映射),因为我们大部分情况下使用的是关系型数据库,如何对数据进行管理?当然 DAO 是一种(这边先不多说),还有就是使用 ORM,它可以让你很方便的进行数据和对象映射转换,如果你的项目是基于事务脚本模式设计的,那就没必要使用 ORM 工具了,因为使用简单的 SQL 更合适,说了这么多,好像都没说到重点,其实仓储协调的是 ORM 中的“O”,也就是对象的概念,它是在数据映射层之上的,是一种概念,而不是一种实现,这个概念很重要。

有时候,仓储和数据访问对象会当作同义词来看待,因为他们都提供了对持久化机制的抽象,在 DAO 中比较好理解,仓储中的持久化机制主要体现在 ORM 中,但是这并不属于仓储,更不属于 DAO,所以有时候我们认为所有的持久化抽象称为 DAO,并不是很准确,我们需要确定的是这种模式是否得到了真正的实现。

仓储和 DAO 是不同的,一个 DAO 主要从数据库表的角度来看待问题,并且提供 CRUD 操作,这种模式适用于事务脚本程序中,这是因为,这些与 DAO 相关的模式通常只是对数据库表的一层封装。而另一方面,仓储和数据影射器(ORM)则更加偏向于对象,因此通常被用于领域模型中。

还有一点内容就是存储过程的探讨,在《实现领域驱动设计》书中,作者也提到了,他不建议我们在基于领域驱动设计的应用中去使用存储过程,因为我们的建模团队并不能很好的理解存储过程所使用的语言,此外,通常来说他们也看不到存储过程的实现,而这些都是有饽于 DDD 目标的,但是有时候使用存储过程是为了程序性能,这是一个取舍的问题,就像我们使用 ORM 一样,我们需要对这个概念进行明确清楚,以防止我们在领域驱动设计的过程中参杂一些其他的东西。

有关仓储和数据访问对象的探讨,最后的结论是,通常来说,你可以将仓储当作 DAO 来看待,但是请注意一点,在设计仓储时,我们应该采用面向集合的方式,而不是面向数据访问的方式。这有助于你将自己的领域当作模型来看待,而不是 CRUD 操作。

以下几段话来自netfocus兄:

  1. 仓储是面向领域的,仓储定义的目的不是db驱动的,仓储管理的数据的最小粒度是聚合根,这两点和DAO有很大不同;
  2. 仓储用于实现聚合的生命周期,聚合创建后,如果不用了,会放回仓储,需要用时,再从仓储取出来(也就是唤醒聚合的意思);所以仓储就是聚合的温床。按照仓储的定义,它是一个集合,所以我们只会为仓储提供类似集合的接口,比如Add,Remove,Get这种操作;因为集合没有Save的说法,所以仓储上不需要有Save,更不会有Commit,也不会有Delete等概念。因为是集合,所以可以理解为一个无限大的内存空间,我们不关心集合是否太大,也不关心背后的持久化,这些不是DDD该思考的东西,我们可以用Dapper来实现,也可以用Mongo,也可以用EF。
  3. Save, Delete, Commit这些都是持久化的概念,最多在应用层表达。

关于仓储(Repository),你必须知道的几个概念。

1,仓储的两种设计方式:面向集合和面向持久化

面向集合和面向持久化,这两种类型的仓储设计方式,在《实现领域驱动设计》中有很详细的讲解,作者还附带了几个具体的实现,比如 Hibernate 实现、TopLink 实现等等,这个必须赞一个,感兴趣的朋友,可以进行阅读下。这面我简单说明下,这两种设计方式的不同之处,举个最直白的例子。

面向集合方式:

this.UserRepository.Add(user);

面向持久化方式:

this.UserRepository.Save(user);

可能很多朋友看到这,会不以为然,需要明确一点,在领域驱动设计中,不论是变量或是方法的命名规则都非常重要,因为其代码就是代表着一种通用语言,你要让人家可以看懂。在面向集合方式中,新对象的添加使用的是 Add,而在面向持久化方式中,不论是新对象的添加或是修改,都是使用的 Save,如果是基于 Unit Of Work(工作单元),会有 Commit。

2,不允许同一聚合实例多次添加到仓储中

关于这一点其实很多人都知道,因为聚合存在唯一性,仓储是管理它的集合,所以不可能在集合中存在多个同一聚合。另外在面向集合方式实现中,当从仓储中获取一个对象并对其进行修改时,我们并不需要“重新保存”该对象到仓储中,因为集合维护了对该对象的引用,而修改将直接作用在该对象上。

3,仓储实现方法返回类型建议为 void

我们在定义仓储接口的时候,一般会这样定义:

bool Add(TAggregateRoot aggregateRoot);

比如添加聚合实例的方法返回值为 bool 类型,但是有时候返回 true 并不一定代表着该聚合实例成功添加到仓储中了,因此,对于仓储来说,返回 void 可能会是更好的方式。那如何判断该聚合实例成功添加到仓储中了呢?我们一般会在仓储实现中进行异常捕获,这一点内容,在书中有讲解,我们可以自定义异常信息,友好的抛出一个异常。

4,对聚合实例的批量操作,最好不要使用 addAll() 和 removeAll() 方法

有时候我们在单个事务中,对多个聚合实例进行添加或删除的时候,为了方便,我们会使用 addAll() 和 removeAll() 方法,但是,我们使用这种方式,并不能对单个聚合实例操作进行监控,建议方式是循环调用 add() 和 remove() 方法。

5,聚合中删除聚合实例的正确表达是什么?

有时候,在应用程序设计中,对实例对象的生命周期管理就代表着其业务逻辑的体现,我们一般在设计中删除对象使用的是 delete,具体表现是从数据库中直接将数据删除掉,这是在事务脚本中的实现方式,在领域驱动设计中,其实是不存在对象删除这一说法的,正确的表达应该是,将聚合实例标记为失活的(disabled),不可用的(unusable),也就是说在仓储所涵盖的内容里面,最好不要出现 delete,至于数据库具体持久化中的 delete,这个就不在仓储的概念之中了。

6,仓储在各层中的位置存放

在书中,作者是这样表述的:我们将仓储接口定义放在了与聚合相同的包中(书中所有的示例都是用 java 实现的),而将仓储中的实现类放在了 impl 子包中,这种方式被大量的 java 项目所采用,然而,在协作上下文中,团队成员们,将实现类放在了基础设施层中。

这一点我是和作者持相同观点,比如下面的解决方案:

7,仓储中的级联删除所引出的问题

关于这个问题,其实我也不是很理解,下面引自作者的一段话(P375):

有人可能会依赖于ORM所提供的生命周期事件来完成对象的级联删除。我刻意地没有使用这种方式,因为我强烈反对由聚合来管理持久化,同时我强烈地提倡只使用资源库来处理持久化。当然,有关这两者的争论非常激烈,并且还在继续。因此,在选择时,你需要多方权衡。但是请记住,DDD专家是不会首先考虑使用聚合来管理持久化的。

根据我的猜测,大概是这样的意思,主要是仓储的持久化管理,一种是使用 ORM 攻击所提供的持久化机制,这种方式就使得仓储依赖于这些技术的实现,但是可以为我们在实现仓储的时候省去很多事,比如我们使用 EntityFramework,你会发现我们在实现仓储的时候,变得异常简单。还有一种方式就是作者提到的,建议让仓储自身去实现持久化机制,但是这种方式实现起来比较复杂,我也没具体的找到其实现方法,这边就不多说。

8,Unit Of Work(工作单元)的使用

只需要记住一点:当 Unit Of Work 中的 commit() 方法执行时,所有发生在对象上的修改都将提交到数据库中。

9,count() or size()?

我们有时候计算聚合实例的总数,一般会将实现方法命名为 count(),但是因为仓储应该尽可能的模拟一个集合,因此建议接口定义如下:

int Size();

命名规则是我们在软件开发过程中,最容易忽略的一点,可能在一般的开发过程中不注意会没事,但是在领域驱动设计中,就像之前所表述的那样,代码代表着一种语言,不光是自己能看懂,还要让需求人员可以看懂,至少可以从名字上知道其代表的意思,这一点很重要。

10,聚合根下的子聚合正确方式

有时,如果我们要获取聚合根下的某些子聚合,我们不用先从资源库中获取到聚合根,然后再从聚合根中获取这些子聚合,而是可以直接从资源库中返回。在有些情况下,这种做法是有好处的。比如,某个聚合根拥有一个很大的实体类型集合,而你需要根据某种查询条件返回该集合中的一部分实体。当然,只有在聚合根中提供了对该实体集合的导航时,我们才能这么做,否则,我们便违背了聚合的设计原则。我建议不要因为客户端的方便而提供这种访问方式。更多的时候,采用这种方式是由于性能上的考虑,比如从聚合根中访问子聚合将带来性能瓶颈的时候。此时的查找方法和其他查找方法具有相同的基本特征,只是它直接返回聚合根下的子聚合,而不是聚合根本身。无论如何,请慎重使用这种方式。

以上是书中作者的观点描述,其实最终也没有表述出一个正确的方式,只是说直接访问子聚合,作者不建议这样做,但是有时候为了一些性能问题,我们又不得不权衡利弊一下。除了这个问题之外,还有一个就是仓储执行完查询后,有时候会返回多个聚合的查询结果对象,这个我们一般会将查询结果放在一个值对象中。

11,CQRS 模式引入

对于 CQRS 模式,我没有深入研究过,更没有实践应用过,我的想法是先去把经典DDD理解透,然后再去尝试其他东西,毕竟路要一步一步走,CQRS 模式是对 DDD 的一种很好补充,也就是说它的产生是有一定的理由的,对于领域驱动设计初学者,我个人不建议,一开始就使用 CQRS 模式。

当我们使用用例优化查询时,有时候我们必须创建多个查询方法,什么意思?就是跨聚合查询,这可能意味着你的聚合边界划分的有问题,如果你确定你的聚合边界划分没有问题,那你应该考虑使用 CQRS 模式了,它的应用场景就是这样,凡事都有产生的原因,如果你的应用程序没有很复杂的查询操作,我个人觉得,完全没必要使用 CQRS 模式,有时候不要为了实现而实现。

12,共享仓储

对于这个概念,我没有深入研究过,作者也只是提出了一个思考,这边也不多说,思考如下:

为不同的聚合类型提供单独的资源库究竟给我们带来了什么好处?在聚合子类较少的情况下,为它们使用单独的资源库可能是最好的方式。但是,随着聚合子类数目的增加,而同时它们又具有完全的可互换性时,使用一个共享的资源库便更合适了。

写在最后

本来想一篇博文写完了事,但是看了下内容,写了还蛮多的,其实都还没说到重点上,只是大致讲述了仓储的概念,为防止大家看得累,那分为上下篇来进行讲解。

下篇主要对:仓储,你的归宿究竟在哪?这个问题进行探讨,内容主要包含其职责及调用场景的可行性探讨,具体用代码来验证。

这一篇内容就到这里,欢迎大家拍砖讨论。

转载于:https://www.cnblogs.com/xishuai/p/ddd_repository.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/575313.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

浏览器基础知识

Web浏览器的主要功能是展示网页资源,即请求服务器并将结果展示在窗口中。工作原理大概如下: 地址栏输入URL 浏览器根据输入的URL查找域名的IP地址,DNS查找过程如下: 浏览器缓存——浏览器会缓存DNS记录一段时间,不同浏…

喜用神最正确的算法_各种电磁仿真算法的优缺点和适用范围(FDTD, FEM和MOM等)...

从实际工程应用的角度谈一下我对这几种算法的理解。先说结论,FDTD算的快但是不精确,可以用来算电大尺寸的物体,要是一个物体的尺寸大于10个波长,一般的服务站是跑不动FEM的,那必须得用FDTD了。FEM最经典的电磁仿真软件…

护士资格证延续注册WEB服务调用失败_服务熔断

熔断机制是应对服务雪崩效应的一种微服务链路保护机制,当扇出链路的某个微服务不可用或者响应时间太长时,会进行服务的降级,进而熔断该节点微服务的调用,快速返回”错误”的响应信息。当检测到该节点微服务响应正常后恢复调用链路…

db2 删除索引_MYSQL进阶——索引

索引模型hash索引hash索引主要适用于等值查询的场景,排序,模糊搜索等场景并不适用有序数组有序数组可用于非等值查询,排序等场景,但是由于写数据时需要对数组中的元素进行位移,所以一般用于静态数据的场景二叉树二叉树…

react 导航条选中颜色_调整安卓手机的颜色以更好地查看一切

并非所有人都有相同的区分颜色的能力。我们对屏幕上色彩配置的需求甚至口味可能会因人而异。幸运的是,Android为我们提供了多种本地工具,能够调整手机的颜色。我们的手机显示数百万种音调,这些音调是由屏幕配置或终端如何解释从某些应用程序接…

linux进程调度之 FIFO 和 RR 调度策略

转载 http://blog.chinaunix.net/uid-24774106-id-3379478.html linux进程调度之 FIFO 和 RR 调度策略 2012-10-19 18:16:43分类: LINUX 作者:manuscola.beangmail.com 博客地址:bean.blog.chinaunix.net 最近花了10几天的时间&#xff0…

echarts 获取点击的y轴数值_有机磷酸催化对醌的不对称直接加成反应合成轴手性芳基醌类化合物...

有机磷酸催化对醌的不对称直接加成反应合成轴手性芳基醌类化合物本文作者:Summer轴手性联芳基二醇骨架广泛存在于天然产物、生物活性分子、有用的手性配体以及催化剂中(Figure 1a),因此,轴手性联芳基二醇化合物的合成受到广泛关注且已经取得了…

H264解码的一个測试程序

网上看到的一个H264视频格式的解码測试程序,能够用来參考其逻辑流程。 代码例如以下: Test_Display_H264(){ in_fd open(H264_INPUT_FILE, O_RDONLY); //video file open fstat(in_fd, &s); …

spring框架 web开发_go语言web开发框架学习:Iris框架讲解(一)

Golang介绍Go语言是谷歌推出的一种全新的编程语言,可以在不损失应用程序性能的情况下降低代码的复杂性。谷歌首席软件工程师罗布派克(Rob Pike)说:我们之所以开发Go,是因为过去10多年间软件开发的难度令人沮丧。谷歌资深软件工程师罗布派克(R…

linux终端的背景_如何在终端显示图像缩略图 | Linux 中国

lsix 是一个简单的命令行实用程序,旨在使用 Sixel 图形格式在终端中显示缩略图。-- Sk不久前,我们讨论了 Fim[1],这是一个轻量级的命令行图像查看器应用程序,用于从命令行显示各种类型的图像,如 bmp、gif、jpeg 和 png…

混合App开发,HBuilder开发移动App

使用HBuilder开发混合App: Hbuilder:是一个在线打包工具,不需要在本地配置开发环境;直接将做好的网站,通过一些简单的操作,就能在线打包为一个App; 混合APP开发常见技术:Html5、Re…

fanuc roboguide_ROBOGUIDE软件:机器人产线输送带输送物料虚拟仿真操作

概述输送带在机器人生产线或工作站中是常见的物料传送设备,它能够将物料从一个工位自动传送到另一个工位,是实现自动化生产制造必不可少的装置设备之一。虚拟仿真是对真实的工业机器人生产线或工作站的图形化再现,因此,对于具有输…

双电阻差分电流采样_小小的采样电阻,还真有点门道!

电流检测电阻的基本原理根据欧姆定律,当被测电流流过电阻时,电阻两端的电压与电流成正比.当1W的电阻通过的电流为几百毫安时,这种设计是没有问题的.然而如果电流达到10-20A,情况就完全不同,因为在电阻上损耗的功率(PI2xR)就不容忽视了.我们可以通过降低电阻阻值来降低功率损耗,…

jpa in查询_优选在shopee虾皮怎么发货价格查询皮皮虾云仓

优选在shopee虾皮怎么发货价格查询皮皮虾云仓皮皮虾云仓物流系统为现代化管理系统,可集中化,高效化的处理本土店物流订单。物流系统可对接主流的的电商平台ERP可以实现高效的订单处理。如lazada,shopee,1688、速卖通、eaby、shopi…

html5+、ReactNative、Weex、Ionic之间的区别、(配置java、python、Android环境)、ReactNative(react-native-cli)、yarn、Weex

html5、ReactNative、Weex、Ionic之间的区别: html5和Ionic: 在开发原理上基本相同,都是需要先开发出一个完整的网站,再通过html5或Ionic提供的打包技术对网站进行打包成移动app,它们实际还是一个网站,并非…

delphi listview失去焦点后的颜色_阴阳师姑获鸟和惠比寿建模更新对比 爷爷帅了 觉醒后鸟姐颜值提升...

阴阳师体验服近期更新了人气式神姑获鸟还有惠比寿的相关建模,本次特别奉上有关这两位式神的建模形象对比图,对比后发现经过修改和优化之后,爷爷更帅了,而觉醒后的鸟姐颜值也有所提升,一起来看看吧。惠比寿觉醒前觉醒前…

kibana 显示 @timestamp 时间问题(utc or browser当前时间)自动转换显示

https://github.com/elasticsearch/kibana/issues/95 可以统一timestamp时间字段为当前信息时区的时间! http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/mapping-date-format.html 转载于:https://www.cnblogs.com/sunxucool/p/3939701.ht…

React简介、虚拟DOM、Diff算法、创建React项目、JSX语法、组件、组件声明方式、组件传值props和state、组件的生命周期

React简介: 前面只是简单介绍移动APP开发,后面还会继续深入介绍移动app开发;其中想要用ReactNative开发出更出色的应用,那么就得学好React,下面将介绍React: React 是一个由 Facebook 开发用于构建用户界…

去掉 edittext 长按全选_开封消毒湿巾全选

开封消毒湿巾全选   其实,带有杀菌效果的清洁产品大多通过降低微生物的繁殖力达到预期的杀菌效果,所添加的每种杀菌成分都针对特定细菌,无法杀灭所有细菌。如果产品中的杀菌剂浓度总是不能将细菌完全杀灭,就可能导致细菌对该类杀…

代数余子式之和怎么算_小明说养老 | 养老金怎么算之月平均缴费指数怎么来的?...

上一期小明分享了企业职工养老保险退休待遇怎么算,具体可戳小明说养老|养老金怎么算?小明来教你。在以张阿姨为例的计算举例中,提到张阿姨15年的平均缴费指数为0.8209。对这个平均缴费指数的概念提问较多,今天就来解释一下月平均缴…