上一篇:《DDD 领域驱动设计-谈谈 Repository、IUnitOfWork 和 IDbContext 的实践(1)

阅读目录:

  1. 抽离 IRepository 并改造 Repository

  2. IUnitOfWork 和 Application Service 的变化

  3. 总结三种设计方案

简单总结上篇所做的两个改进:

  • 从 Repository 和 UnitOfWork 中抽离出 IDbContext,并且它们只依赖于 IDbContext。
  • Repository 和 UnitOfWork 为平级关系,UnitOfWork 负责维护对象状态(增删改),Repository 负责获取对象(查)。

后来,园友 Qlin 在评论中,提出了另外一种方式,大致为:

  • Repository 和 UnitOfWork 还是依赖于 IDbContext。
  • UnitOfWork 只有 Commit,Repository 提供对象的所有操作(增删改查)。

这篇文章我们就按照这种方式实现一下,关于 Repository、IUnitOfWork 和 IDbContext 的设计,以及 Application Service 的调用,上面是两种设计方案,加上上一篇博文开头说到的一种方案,我大致总结了三种,关于它们的优缺点,文章最后我再进行总结。

另外,关于 IDbContext 的接口设计,其实是有些模糊的,因为它并没有真正解耦 EF,比如 DbSet<TEntity> Set<TEntity>() 还是依赖于 EF,没办法,就像我们在 Repository 中返回 IQueryable,你在 Application Service 调用的时候,也必须引用 EF 一样,对于 IDbContext 来说,我们暂时把它看作是一个数据上下文容器,所有对象的持久化最后都通过它来完成,因为我们的解决方案暂时只能使用 EF,所以对于 IDbContext,我们先暂时这样设计。

下面我们开始进行设计。

1. 抽离 IRepository 并改造 Repository

抽离 IRepository 啥意思?我们直接来看下代码:

  1. namespace DDD.Sample.Domain.IRepository
  2. {
  3. public interface IRepository<TAggregateRoot>
  4. where TAggregateRoot : class, IAggregateRoot
  5. {
  6. void Add(TAggregateRoot aggregateRoot);
  7. void Update(TAggregateRoot aggregateRoot);
  8. void Delete(TAggregateRoot aggregateRoot);
  9. TAggregateRoot Get(int id);
  10. }
  11. }

IRepository 是一个泛型接口,类型为 IAggregateRoot,我们在里面定义了增删改查的常用操作,它的作用就是减少 Repository 的冗余代码,我们看下 IStudentRepository 的定义:

  1. namespace DDD.Sample.Domain.IRepository
  2. {
  3. public interface IStudentRepository : IRepository<Student>
  4. {
  5. Student GetByName(string name);
  6. }
  7. }

IStudentRepository 需要继承 IRepository,并确定泛型类型为 Student,Student 继承自 IAggregateRoot,因为增删改查常用操作已经定义,所以我们在其它类似的 IStudentRepository 中就不需要定义了。

IRepository 需要进行实现,如果在 StudentRepository 中进行实现,就没有什么作用了,所以我们需要一个 BaseRepository 来实现 IRepository:

  1. namespace DDD.Sample.Repository
  2. {
  3. public abstract class BaseRepository<TAggregateRoot> : IRepository<TAggregateRoot>
  4. where TAggregateRoot : class, IAggregateRoot
  5. {
  6. public readonly IDbContext _dbContext;
  7. public BaseRepository(IDbContext dbContext)
  8. {
  9. _dbContext = dbContext;
  10. }
  11. public void Add(TAggregateRoot aggregateRoot)
  12. {
  13. _dbContext.Set<TAggregateRoot>().Add(aggregateRoot);
  14. }
  15. public void Update(TAggregateRoot aggregateRoot)
  16. {
  17. _dbContext.Entry<TAggregateRoot>(aggregateRoot).State = EntityState.Modified;
  18. }
  19. public void Delete(TAggregateRoot aggregateRoot)
  20. {
  21. _dbContext.Set<TAggregateRoot>().Remove(aggregateRoot);
  22. }
  23. public TAggregateRoot Get(int id)
  24. {
  25. return _dbContext.Set<TAggregateRoot>().FirstOrDefault(t => t.Id == id);
  26. }
  27. }
  28. }

咋一看 BaseRepository 有点像我们上篇的 UnitOfWork,因为我们把增删改放在 Repository 了,因为 Repository 还是和 UnitOfWork 为平级关系,所以我们在 Repository 中用的 IDbContext 而非 IUnitOfWork,这个没什么问题,我们看下 StudentRepository 的具体实现:

  1. namespace DDD.Sample.Repository
  2. {
  3. public class StudentRepository : BaseRepository<Student>, IStudentRepository
  4. {
  5. public StudentRepository(IDbContext dbContext)
  6. : base(dbContext)
  7. {
  8. }
  9. public Student GetByName(string name)
  10. {
  11. return base._dbContext.Set<Student>().Where(x => x.Name == name).FirstOrDefault();
  12. }
  13. }
  14. }

StudentRepository 很简单,因为常用操作 BaseRepository 已经实现了,base(dbContext) 的作用就是给 BaseRepository 注入 IDbContext 对象。

Repository 的改造基本上就这些,表面看起来确实很好,另外,如果没有 IUnitOfWork 和 Application Service,我们对 Domain 进行单元测试,也是能满足我们的需求,但需要将 IDbContext 再进行修改下。

2. IUnitOfWork 和 Application Service 的变化

我们先看下 IUnitOfWork 的变化,直接贴下代码:

  1. namespace DDD.Sample.Infrastructure.Interfaces
  2. {
  3. public interface IUnitOfWork
  4. {
  5. bool Commit();
  6. void Rollback();
  7. }
  8. }

因为增删改都移到 Repository 中了,所以 IUnitOfWork 的工作就很简单,只有 Commit 和 Rollback,实现也比较简单,我们看下:

  1. namespace DDD.Sample.Infrastructure
  2. {
  3. public class UnitOfWork : IUnitOfWork
  4. {
  5. private IDbContext _dbContext;
  6. public UnitOfWork(IDbContext dbContext)
  7. {
  8. _dbContext = dbContext;
  9. }
  10. public bool Commit()
  11. {
  12. return _dbContext.SaveChanges() > 0;
  13. }
  14. public void Rollback()
  15. {
  16. throw new NotImplementedException();
  17. }
  18. }
  19. }

这个没啥说的,我们直接看下 Application Service 的代码:

  1. namespace DDD.Sample.Application
  2. {
  3. public class StudentService : IStudentService
  4. {
  5. private IUnitOfWork _unitOfWork;
  6. private IStudentRepository _studentRepository;
  7. private ITeacherRepository _teacherRepository;
  8. public StudentService(IUnitOfWork unitOfWork,
  9. IStudentRepository studentRepository,
  10. ITeacherRepository teacherRepository)
  11. {
  12. _unitOfWork = unitOfWork;
  13. _studentRepository = studentRepository;
  14. _teacherRepository = teacherRepository;
  15. }
  16. public Student Get(int id)
  17. {
  18. return _studentRepository.Get(id);
  19. }
  20. public bool Add(string name)
  21. {
  22. var student = new Student { Name = name };
  23. var teacher = _teacherRepository.Get(1);
  24. teacher.StudentCount++;
  25. _studentRepository.Add(student);
  26. _teacherRepository.Update(teacher);
  27. return _unitOfWork.Commit();
  28. }
  29. }
  30. }

StudentService 其实变化不大,只是将原来的 _unitOfWork 添加修改操作,改成了 _studentRepository 和 _teacherRepository,执行下 StudentService.Add 的单元测试代码,发现执行不通过,为什么呢?因为 Repository 和 UnitOfWork 的 IDbContext 不是同一个对象,添加修改对象通过 Repository 注册到 IDbContext 中,最后 UnitOfWork 执行 Commit 却是另一个 IDbContext,所以我们需要确保 Repository 和 UnitOfWork 共享一个 IDbContext 对象,怎么实现呢?

我们进行改造下:

  1. namespace DDD.Sample.Application
  2. {
  3. public class StudentService : IStudentService
  4. {
  5. private IDbContext _dbContext;
  6. private IUnitOfWork _unitOfWork;
  7. private IStudentRepository _studentRepository;
  8. private ITeacherRepository _teacherRepository;
  9. public StudentService(IDbContext dbContext)
  10. {
  11. _dbContext = dbContext;
  12. }
  13. public Student Get(int id)
  14. {
  15. _studentRepository = new StudentRepository(_dbContext);
  16. return _studentRepository.Get(id);
  17. }
  18. public bool Add(string name)
  19. {
  20. _unitOfWork = new UnitOfWork(_dbContext);
  21. _studentRepository = new StudentRepository(_dbContext);
  22. _teacherRepository = new TeacherRepository(_dbContext);
  23. var student = new Student { Name = name };
  24. var teacher = _teacherRepository.Get(1);
  25. teacher.StudentCount++;
  26. _studentRepository.Add(student);
  27. _teacherRepository.Update(teacher);
  28. return _unitOfWork.Commit();
  29. }
  30. }
  31. }

上面对应的测试代码执行通过,其实解决方式很简单,就是手动给 UnitOfWork、StudentRepository 和 TeacherRepository 注入相同的 IDbContext 对象,当然这是一种解决方式,还有人喜欢用属性注入,这都是可以的,无非最后就是想让 Repository 和 UnitOfWork 共享一个 IDbContext 对象。

本篇的相关代码已提交到 GitHub,大家可以参考下:https://github.com/yuezhongxin/DDD.Sample

3. 总结三种设计方案

关于 Repository、IUnitOfWork 和 IDbContext 的设计,以及 Application Service 的调用,我总结了三种设计方式,我觉得也是我们常用的几种方式,下面我大致分别说下。

1. IUnitOfWork -> EfUnitOfWork -> Repository -> Application Service

这种设计应该我们最熟悉,因为我们一开始就是这样设计的,但问题也是最多的,要不然我也不会写上一篇博文了,比如存在的问题:

  • IUnitOfWork 的职责不明确。
  • Repository 的职责不明确。
  • Application Service 很困惑,因为它不知道该使用谁。
  • Application Service 的代码越来越乱。
  • ....

上一篇博文最后分析出来是 IUnitOfWork 的设计问题,因为它做了太多的事,并且 Repository 依赖于 IUnitOfWork,以至于最后在 Application Service 的调用中,Repository 显得非常多余,这种设计最大的问题就是职责不明确

2. IDbContext -> IUnitOfWork/IRepository(only query) -> UnitOfWork/Repository -> Application Service

第二种设计是我比较倾向于的,因为第一种设计出现的问题,所以我对 IUnitOfWork 的设计非常看重,并且我读了《企业应用架构模式》中关于 UnitOfWork 的所有内容,其实就那么几个字可以概括:维护对象状态,统一提交更改。我个人觉得架构设计最重要的地方就是底层接口的设计,就像我们盖一栋摩天大楼,如果地基打不稳,最后的结果肯定是垮塌,所以,我比较坚持 IUnitOfWork 这样的设计:

相对于第一种设计,这种设计还有一个不同就是 IUnitOfWork 和 IRepository 为平级关系,为什么这样设计?因为我们不能通过 IUnitOfWork 提供查询操作,并且 IUnitOfWork 和 ORM 也没什么关系,所以我们最后抽离出来一个 IDbContext,并且用 EF 去实现它。

IRepository 只有查询,这是我们的定义,在 Application Service 的调用中,对象的新增和修改都是通过 IUnitOfWork 进行实现的,因为查询并不需要记录状态,所以我们并不需要将 IDbContext 在 IUnitOfWork 和 IRepository 之间进行共享,有人会说,IRepository 应该提供领域对象的增删改操作啊,我们再看下 Repository 的定义:协调领域和数据映射层,利用类似于集合的接口来访问领域对象。

集合访问领域对象,那 Repository 如果这样设计呢:

  1. public class StudentRepository : IStudentRepository
  2. {
  3. private IQueryable<Student> _students;
  4. public StudentRepository(IDbContext dbContext)
  5. {
  6. _students = dbContext.Set<Student>();
  7. }
  8. public Student GetByName(string name)
  9. {
  10. return _students.Where(x => x.Name == name).FirstOrDefault();
  11. }
  12. }

这种 Repository 设计是比较符合定义的,另外,我们如果对 Domain 进行单元测试,集合性质的领域对象也是可以进行维护的,只不过没有持久化而已。

总的来说,第二种设计最大的优点就是职责明确,你想干坏事也干不了(因为接口已经被约束),目前来说没发现什么问题。

3. IDbContext -> IUnitOfWork(only commit)/IRepository -> UnitOfWork/Repository -> Application Service

第三种设计就是本篇博文讲述的,它其实是从第一种和第二种之间取一个中间值,做了一些妥协工作,具体的实现,上面已经详细说明了,我最接受不了的是对 IUnitOfWork 的更改,虽然表面看起来蛮好的,但我总觉得有些不对劲的地方,就像我们“迫于现实做一些违背道德的事”,可能现在觉察不到什么,但出来混的总是要还的。

关于 Repository、IUnitOfWork 和 IDbContext 的设计,以及 Application Service 的调用,我觉得应该是我们在 DDD 架构设计过程中,最普遍遇到的一个问题,但也是最困惑的一个问题,比如最近两个园友写的博文:

对于本篇博文,如果你有什么问题或疑问,欢迎探讨学习。

DDD 领域驱动设计-谈谈 Repository、IUnitOfWork 和 IDbContext 的实践(2)的更多相关文章

  1. DDD领域驱动设计仓储Repository

    DDD领域驱动设计初探(二):仓储Repository(上) 前言:上篇介绍了DDD设计Demo里面的聚合划分以及实体和聚合根的设计,这章继续来说说DDD里面最具争议的话题之一的仓储Repositor ...

  2. DDD 领域驱动设计-谈谈 Repository、IUnitOfWork 和 IDbContext 的实践(3)

    上一篇:<DDD 领域驱动设计-谈谈 Repository.IUnitOfWork 和 IDbContext 的实践(2)> 这篇文章主要是对 DDD.Sample 框架增加 Transa ...

  3. DDD 领域驱动设计-谈谈 Repository、IUnitOfWork 和 IDbContext 的实践(1)

    好久没写 DDD 领域驱动设计相关的文章了,嘎嘎!!! 这几天在开发一个新的项目,虽然不是基于领域驱动设计的,但我想把 DDD 架构设计的一些东西运用在上面,但发现了很多问题,这些在之前的短消息项目中 ...

  4. DDD 领域驱动设计-谈谈 Repository、IUnitOfWork 和 IDbContext 的实践(转)

    http://www.cnblogs.com/xishuai/p/ddd-repository-iunitofwork-and-idbcontext.html 好久没写 DDD 领域驱动设计相关的文章 ...

  5. C#进阶系列——DDD领域驱动设计初探(二):仓储Repository(上)

    前言:上篇介绍了DDD设计Demo里面的聚合划分以及实体和聚合根的设计,这章继续来说说DDD里面最具争议的话题之一的仓储Repository,为什么Repository会有这么大的争议,博主认为主要原 ...

  6. DDD领域驱动设计初探(二):仓储Repository(上)

    前言:上篇介绍了DDD设计Demo里面的聚合划分以及实体和聚合根的设计,这章继续来说说DDD里面最具争议的话题之一的仓储Repository,为什么Repository会有这么大的争议,博主认为主要原 ...

  7. C#进阶系列——DDD领域驱动设计初探(三):仓储Repository(下)

    前言:上篇介绍了下仓储的代码架构示例以及简单分析了仓储了使用优势.本章还是继续来完善下仓储的设计.上章说了,仓储的最主要作用的分离领域层和具体的技术架构,使得领域层更加专注领域逻辑.那么涉及到具体的实 ...

  8. DDD领域驱动设计初探(三):仓储Repository(下)

    前言:上篇介绍了下仓储的代码架构示例以及简单分析了仓储了使用优势.本章还是继续来完善下仓储的设计.上章说了,仓储的最主要作用的分离领域层和具体的技术架构,使得领域层更加专注领域逻辑.那么涉及到具体的实 ...

  9. DDD 领域驱动设计-如何完善 Domain Model(领域模型)?

    上一篇:<DDD 领域驱动设计-如何 DDD?> 开源地址:https://github.com/yuezhongxin/CNBlogs.Apply.Sample(代码已更新) 阅读目录: ...

随机推荐

  1. Angular企业级开发(5)-项目框架搭建

    1.AngularJS Seed项目目录结构 AngularJS官方网站提供了一个angular-phonecat项目,另外一个就是Angular-Seed项目.所以大多数团队会基于Angular-S ...

  2. js复杂对象和简单对象的简单转化

    var course = { teacher :{ teacherId:001, teacherName:"王" }, course : { courseId : 120, cou ...

  3. OpenCASCADE Shape Location

    OpenCASCADE Shape Location eryar@163.com Abstract. The TopLoc package of OpenCASCADE gives resources ...

  4. 利用XAG在RAC环境下实现GoldenGate自动Failover

    概述 在RAC环境下配置OGG,要想实现RAC节点故障时,OGG能自动的failover到正常节点,要保证两点: 1. OGG的checkpoint,trail,BR文件放置在共享的集群文件系统上,R ...

  5. android studio你可能忽视的细节——启动白屏?drawable和mipmap出现的意义?这里都有!!!

    android studio用了很久了,也不知道各位小伙伴有没有还在用eclipse的,如果还有,楼主真心推荐转到android studio来吧,毕竟亲儿子,你会知道除了启动速度稍微慢些,你找不到一 ...

  6. C++整数转字符串的一种方法

    #include <sstream> //ostringstream, ostringstream::str() ostringstream stream; stream << ...

  7. PHP设计模式(八)桥接模式(Bridge For PHP)

    一.概述 桥接模式:将两个原本不相关的类结合在一起,然后利用两个类中的方法和属性,输出一份新的结果. 二.案例 1.模拟毛笔(转) 需求:现在需要准备三种粗细(大中小),并且有五种颜色的比 如果使用蜡 ...

  8. 《MySQL必知必会》学习笔记

    数据库:数据库是一种以某种有组织的方式存储的数据集合.其本质就是一个容器,通常是一个或者一组文件. 表:表示一种结构化的文件,可用来存储某种特定类型的数据. 模式:描述数据库中特定的表以及整个数据库和 ...

  9. linux字符串url编码与解码

    编码的两种方式 echo '手机' | tr -d '\n' | xxd -plain | sed 's/\(..\)/%\1/g' echo '手机' |tr -d '\n' |od -An -tx ...

  10. photoshop:无法完成请求 因为暂存盘已满

    今天photoshop打开一个问题,提醒:无法完成请求因为暂存盘已满 不用担心这个问题很好解决可能是你做的图比较大并不需要清理C盘空间 选择:编辑→首选项→暂存盘 设置第一暂存盘为D盘或E盘 总之 第 ...