应用程序框架实战二十一:DDD分层架构之仓储(介绍篇)

  前面已经介绍过Entity Framework的工作单元和映射层超类型的封装,从本文开始,将逐步介绍仓储以及对查询的扩展支持。

什么是仓储

  仓储表示聚合的集合。

  仓储所表现出来的集合外观,仅仅是一种模拟,除了测试以外,没有理由使用内存中真正的集合来创建仓储。

  不应该为所有实体建立仓储,只有聚合才拥有仓储。

  仓储用来重建已持久化的聚合,而工厂用于新建聚合。

使用仓储的优点

  直接使用Entity Framework的DbContext不是很好吗,为什么还要在DbContext的上方封装一层仓储呢,这是否多此一举?

  很多使用EF的程序员确实是直接使用DbContext,并且他们发现开发起来十分简单,因为DbContext的接口设计得非常优雅,从接口上看,DbContext就好像所有实体集合的仓库。

  另一方面,很多使用了仓储的朋友,都是依葫芦画瓢,虽然创建了仓储,但并没有体会到多大好处。

  下面简要介绍使用仓储将为你带来的优点。

从概念上简化数据操作

  仓储模拟了某种聚合的集合,而DbContext包含了N种类型的集合。

  与仓储相近的一个数据访问模式是数据访问对象(DAO)模式,很多人认为仓储不过是数据访问对象换了一个名词而已,从技术上说的确如此,仓储的强大之处在于概念上更简单。仓储在概念上代表内存中的集合操作,而数据访问对象代表数据库操作,很明显,集合比数据库在概念上更简单。

减少冗余查询逻辑

  如果直接使用DbContext,由于特定查询逻辑没有一个固定位置,可能分散到任何地方,这很容易造成冗余。

  仓储是封装特定查询逻辑的好地方,对于特定的查询逻辑,放到与聚合相应的仓储中即可。

降低耦合度

  直接使用DbContext,所有调用代码与EF实现高度耦合。

  另一方面,由于DbContext能够获取任意实体,这些实体可能位于聚合内部,这样会破坏聚合的封装性,同时在任意位置可以获取任意对象,由于缺乏约束力而导致更高的耦合。

方便单元测试

  直接使用DbContext只能进行集成测试,必须连接到真实数据库。而领域层只持有仓储接口,所以测试时很容易替换成模拟实现,从而避开数据库。

使用仓储的要点

使用更具体的仓储

  一般来讲,ICustomerRepository比直接使用泛型的IRepository<Customer>要好。

  为了定义通用操作,我们会创建一些泛型基类来实现基础操作。

  有些人发现很多具体仓储只是直接继承泛型仓储,比如ICustomerRepository和CustomerRepository从泛型的IRepository<Customer>和Repository<Customer>派生,但CustomerRepository本身并没有其它什么代码。于是很多人学会偷懒,直接使用泛型基类进行操作。

  ICustomerRepository优于IRepository<Customer>的原因如下。

  1.  概念上更清晰。

  ICustomerRepository从概念上良好表达了该仓储用于操作客户,而泛型仓储可以操作任何聚合。

  2.  为特定查询逻辑提供唯一封装和访问点。

  ICustomerRepository可以将客户相关的各种复杂查询封装起来,而IRepository<Customer>很难对客户查询进行封装,一个办法是使用扩展方法来完成封装,不过比起CustomerRepository要麻烦得多,而且实现代码也更难管理。

  3.  降低耦合。

  泛型IRepository<>可以在任意位置获取任意聚合,这比起DbContext要强一些,不会破坏聚合的封装性,但缺乏约束力仍然会导致更高耦合。

  ICustomerRepository通过明确的声明,只能获取需要的聚合,从而控制了对象的访问。

仓储仅返回与其聚合高度相关的内容

  仓储可以返回对应的聚合,也可以返回聚合相关的统计信息,甚至可以返回聚合的一个子集。

  有人在仓储查询中使用匿名对象,使用匿名对象的原因很简单,即为了进行投影操作,这样可以限制Sql查询返回的列,对于Sql性能调优来讲,这可能非常重要,比如使用Sql Server的覆盖索引。

  但是使用匿名对象进行查询有很多问题,一个问题是无法直接作为结果返回。有人为了省力,直接使用聚合或其部分子对象作为返回类型,其结果是对象中的一部分属性被赋值,另一部分属性为null。这可能导致许多神秘的Bug,另外导致你或其它人对仓储查询信心不足,因为这些查询并不安全,你每当需要调用一个查询,首先得仔细检查代码,看看哪些值是空的,以免踩到地雷。

  如果要返回聚合的一个子集,需要单独定义一些对象,以作为返回类型。工作量虽然比较大,但更加安全和清晰。

  如果需要获取的内容由多个聚合组成,这个查询操作应该放入哪个仓储中?这种情况放到哪个仓储其实都不合适,更好的办法是使用一个查询服务,就好像跨越多个聚合的业务操作需要领域服务一样。

总结

1. 仓储的定义

  仓储表示聚合的集合。

2. 仓储的优点

  • 简化数据操作
  • 减少冗余查询逻辑
  • 降低耦合度
  • 方便单元测试

3. 仓储的要点

  • 使用更具体的仓储
  • 仓储查询仅返回与其聚合高度相关的内容

  由于仓储是对数据操作的封装,包括仓储基础,分页,查询扩展,查询对象,查询条件(规约模式),查询条件应用(日期范围与数值范围查询)等内容,所以需要多篇文章进行介绍。

  .Net应用程序框架交流QQ群: 386092459,欢迎有兴趣的朋友加入讨论。

  谢谢大家的持续关注,我的博客地址:http://www.cnblogs.com/xiadao521/

时间: 2024-10-07 07:40:43

应用程序框架实战二十一:DDD分层架构之仓储(介绍篇)的相关文章

应用程序框架实战十五:DDD分层架构之领域实体(验证篇)

在应用程序框架实战十四:DDD分层架构之领域实体(基础篇)一文中,我介绍了领域实体的基础,包括标识.相等性比较.输出实体状态等.本文将介绍领域实体的一个核心内容——验证,它是应用程序健壮性的基石.为了完成领域实体的验证,我们在前面已经准备好了验证公共操作类和异常公共操作类. .Net提供的DataAnnotations验证方法非常强大,Mvc会自动将DataAnnotations特性转换为客户端Js验证,从而提升了用户体验.但是客户端验证是靠不住的,因为很容易绕开界面向服务端提交数据,所以服务端

应用程序框架实战十八:DDD分层架构之聚合

前面已经介绍了DDD分层架构的实体和值对象,本文将介绍聚合以及与其高度相关的并发主题. 我在之前已经说过,初学者第一步需要将业务逻辑尽量放到实体或值对象中,给实体“充血”,这样可以让业务逻辑高度内聚,并为你提供业务逻辑的唯一访问点.而聚合则是第二步,它将多个相关业务概念包装到单一的概念中,从而大幅简化系统设计,由于受传统数据建模思维影响,我在聚合方面吃过大亏,花了将近一年才真正用起来,为了你少走弯路,我会把一些要点总结出来供你参考. 什么是聚合? 聚合包装一组高度相关的对象,作为一个数据修改的单

应用程序框架实战十六:DDD分层架构之值对象(介绍篇)

前面介绍了DDD分层架构的实体,并完成了实体层超类型的开发,同时提供了验证方面的支持.本篇将介绍另一个重要的构造块——值对象,它是聚合中的主要成分. 如果说你已经在使用DDD分层架构,但你却从来没有使用过值对象,这毫不奇怪,因为多年来养成的数据建模思维已经牢牢把你禁锢,以致于你在使用面向对象方式进行开发时,还是以数据为中心. 当我们完成了基本的需求分析以后,如果说需要进行设计,那么你能想到的就是数据库表及表关系的设计,这就是数据建模.数据建模的主要依据是数据库范式设计,根据要求严格程度的递增分为

应用程序框架实战十四:DDD分层架构之领域实体(基础篇)

上一篇,我介绍了自己在DDD分层架构方面的一些感想,本文开始介绍领域层的实体,代码主要参考自<领域驱动设计C#2008实现>,另外参考了网上找到的一些示例代码. 什么是实体 由标识来区分的对象称为实体. 实体的定义隐藏了几个信息: 两个实体对象,只要它们的标识属性值相等,哪怕标识属性以外的所有属性值都不相等,这两个对象也认为是同一个实体,这意味着两个对象是同一实体在其生命周期内的不同阶段. 为了能正确区分实体,标识必须唯一. 实体的标识属性值是不可变的,标识属性以外的属性值是可变的.如果标识值

应用程序框架实战二十七: 基于Mvc+EasyUi+EF+Autofac的CRUD DEMO免费发放,纯干货,附截图

不知不觉,这个系列已经写了好几十篇了.我本来打算把基础介绍完再发放Demo进行整体说明,不过大部分人更喜欢看得见摸得着的表现层,对后端不是太感兴趣,所以我决定先发一个简单的CRUD Demo出来,让大家先感受一下,被应用程序框架封装之后的代码大体是什么样子. 采用EasyUi作为前端框架,主要是它比Dwz强大,另外也是基于Html扩展,比更强大的Ext要简单得多,更重要的是它越来越流行了,对于更详细的决择或前端架构设计,我会在后续文章说明. 虽然是一个简单的单表CRUD操作,但是分层架构和各方面

应用程序框架实战二十三:基础查询扩展

上面两篇已经作好准备,本文将进行基础查询扩展.当使用了Entity Framework这样的ORM框架以后,我们查询的核心被集中在IQueryable的Where方法上. 如果UI需要通过姓名查询一个客户,会在UI上放置一个输入框作为客户姓名的查询条件.服务端接收以后通过Where方法进行过滤,如下所示,entities表示DbContext的子类. var queryable = entities.Customers.Where( t => t.Name == name ); 当然,也可以使用

应用程序框架实战二十六:查询对象

信息系统的查询需求千变万化,在仓储中为每个查询需求创建一个特殊方法,将导致大量乏味而臃肿的接口. 一种更加可行的办法是,在应用层服务中描述查询需求,并通过仓储执行查询. 为了能够更好的描述查询需求,可以将查询功能从仓储中抽取出来,专门创建一个查询对象. 查询最复杂的部分是条件过滤,这也是查询对象的主要职责.查询对象可以认为是规约模式的一个变种,允许查询对象动态创建查询条件. 在Util.Domains项目Repositories目录中,创建查询对象基接口IQueryBase,代码如下. usin

应用程序框架实战二十五:查询条件(规约模式应用)

前面已经做了一些准备工作,本篇将介绍查询条件的封装,它是规约模式的一个应用. 规约使用一个对象来封装谓词,我之前已经介绍过它在验证方面的应用,本篇是规约模式在查询方面的应用. 规约的强大之处在于,能够将一堆杂乱无章的条件判断或查询条件封装起来,以一个清晰的概念来表达,并使得这些谓词具备了可复用的能力. 首先在Util.Domains项目的Repositories目录中创建ICriteria接口,这个接口表示一个查询条件,代码如下. using System; using System.Linq.

应用程序框架实战二十四:基础查询扩展 - 分页与排序

上一篇介绍了IQueryable的Where方法存在的问题,并扩展了一个名为Filter的过滤方法,它是Where方法的增强版.本篇将介绍查询的另一个重要主题——分页与排序. 对于任何一个信息系统,查询都需要分页,因为不可能直接返回表中的所有数据. 如果直接使用原始的Ado.Net,我们可以编写一个通用分页存储过程来进行分页查询,然后通过一个DataTable返回给业务层.不过进入Entity Framework时代,分页变得异常简单,通过Skip和Take两个方法配合就可以完成任务. 为了让分