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,一经查实,立即删除!

相关文章

node中模板引擎、模块导出、package.json简介

在node.js中使用引擎模板: art-template不仅在浏览器可以使用,也可以在node中使用,并且模板引擎起早诞生于服务器领域,在node中使用模板引擎: 1.安装:在一个文件目录下执行命令:npm install a…

富爸爸穷爸爸

推荐大家看看,虽然我只看了第一章,但是我觉得写的确实挺好的,也确实符合这个社会规律。 老实本分的工作已经是过去式了。为自己工作才是出路。转载于:https://www.cnblogs.com/joysky/p/3909127.html

Java和C++在细节上的差异(转)

Java的基本程序结构、关键字、操作符都和C/C非常相似,以下为主要的几点区别: 一、基本程序设计结构: Java的基本程序结构、关键字、操作符都和C/C非常相似,以下为主要的几点区别: 1. Java的原始数值型数据类型中不包含无符号类型&…

文件路径和模块路径、nodemon工具

文件路径和模块路径: //在文件操作相对路径中,前面的 ./ 可以省略,但是 在模块标识路径中 ,前面的 ./ 不能省略。// ./-----表示相对于当前目录 /-------当前文件所属磁盘根目录 var fs require(fs);fs.readFile(t…

Express框架简介、express使用模块引擎、模式数据

Express简介: 原生的http不足以应对我们的开发需求,所以我们需要使用框架来加快我们的开发,这里推荐expressjs,其官网:expressjs.com,中文文档推荐:http://javascript.ruanyifeng.com/nodejs/e…

数据库字段关联更新

MS SQL Server 子查询更新: update log set uin b.uin from log a,logs b where a.accountuin b.accountuin mysql 更新: update t_stat_month_user a INNER JOIN t_dept b on a.op_deptb.op_id set a.dept_short_nameb.dept_short_name;转载于:https:/…

浏览器基础知识

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

MongoDB简介、在node中使用MongoDB

MongoDB数据库简介: 使用MongoDB的好处是不用SQL语句,它提供了对应的API,其功能和MYSQL基本相同,是最像关系型数据库的非关系型数据库;不需要设计表的结构,文档相当于json,如果想要了解更多&am…

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

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

Linux下实现自动设置SSH代理

SSH的巨大价值体现在能够配置为代理服务器上。不像在Windows下每次还需要手动登录设置,Linux有很好的工具链能够实现自动设置SSH代理,就是expect和ssh的联合使用,再加上proxychains,任何程序都可以享用代理了,在此我简…

node中操作MySQL

node操作MySQL数据库: 在node中操作MySQL数据库的基本流程如下: // node操作MySQL需要在npm官网下载mysql包并载入node执行代码:// 1.载入MySQL数据库包var mysql require(mysql);// 2.创建连接:var connection mysql.createCo…

百度UEditor编辑器使用(二)

本文摘自:http://www.cnblogs.com/pmpen/archive/2011/09/19/2181811.html 首先感谢分享 百度WEB前端设计部推出一款开源的编辑器UEditor http://ueditor.baidu.com/index.html ,这款编辑器相当强大,它提供了百度地图,google地图…

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

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

AssetManager asset的使用

Android 系统为每一个新设计的程序提供了/assets文件夹,这个文件夹保存的文件能够打包在程序里。/res 和/assets的不同点是,android不为/assets下的文件生成ID。假设使用/assets下的文件,须要指定文件的路径和文件名称。以下这个样例&#xf…

vmware 快照用关机吗_PS板绘上色的骚操作,打破初学者上色残的瓶颈!|快照|绘画|初学者|配色|色阶...

PS板绘上色的骚操作,打破初学者上色残的瓶颈!初学者如何入门绘画?学习板画难吗?怎样才能学习好绘画?想必这些都是绘画初学者们经常在想的问题吧,就是不知道如何才能学习好绘画,然后绘画出自己想…

vue概述、vue文件特点、vue核心思想、双向数据流、单文件、启动一个vue项目、声明式渲染

vue介绍: Vue:当前较火的MVVM框架,轻量、简介、高效、组件化、数据驱动,模块和渲染函数的弹性选择,简单的语法及项目创建,渲染速度极快,基于Virtual Dom,利用虚拟DOM实现快速渲染&a…

jQuery特效手风琴特效 手写手风琴网页特效

今天写一个简单的手风琴效果,不用插件,利用强大的jQuery,写一个手风琴效果。 页面预览,请点击这里预览: 手风琴预览案例分析: html结构 就是一个大盒子里面放着5个li,每个li的小小是200像素&a…

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

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

javascript中es6语法

es6语法简介: // 1.历史:// 1995-----JavaScript诞生// 1997-----ECMAScript标准确立// 1999-----ES3出现,与此同时IE5风靡一时// 2009-----ES5出现,现在绝大所数使用的是ES5// 2015-----ES6/ECMAScript2015出现// 2.函数的Rest参…

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

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