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

原做者: 田园里的蟋蟀 

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

阅读目录:git

  1. 抽离 IRepository 并改造 Repositorygithub

  2. IUnitOfWork 和 Application Service 的变化markdown

  3. 总结三种设计方案架构

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

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

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

  • 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 啥意思?咱们直接来看下代码:

namespace DDD.Sample.Domain.IRepository { public interface IRepository<TAggregateRoot> where TAggregateRoot : class, IAggregateRoot { void Add(TAggregateRoot aggregateRoot); void Update(TAggregateRoot aggregateRoot); void Delete(TAggregateRoot aggregateRoot); TAggregateRoot Get(int id); } }

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

namespace DDD.Sample.Domain.IRepository { public interface IStudentRepository : IRepository<Student> { Student GetByName(string name); } }

IStudentRepository 须要继承 IRepository,并肯定泛型类型为 Student,Student 继承自 IAggregateRoot,由于增删改查经常使用操做已经定义,因此咱们在其它相似的 IStudentRepository 中就不须要定义了。

IRepository 须要进行实现,若是在 StudentRepository 中进行实现,就没有什么做用了,因此咱们须要一个 BaseRepository 来实现 IRepository:

namespace DDD.Sample.Repository { public abstract class BaseRepository<TAggregateRoot> : IRepository<TAggregateRoot> where TAggregateRoot : class, IAggregateRoot { public readonly IDbContext _dbContext; public BaseRepository(IDbContext dbContext) { _dbContext = dbContext; } public void Add(TAggregateRoot aggregateRoot) { _dbContext.Set<TAggregateRoot>().Add(aggregateRoot); } public void Update(TAggregateRoot aggregateRoot) { _dbContext.Entry<TAggregateRoot>(aggregateRoot).State = EntityState.Modified; } public void Delete(TAggregateRoot aggregateRoot) { _dbContext.Set<TAggregateRoot>().Remove(aggregateRoot); } public TAggregateRoot Get(int id) { return _dbContext.Set<TAggregateRoot>().FirstOrDefault(t => t.Id == id); } } }

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

namespace DDD.Sample.Repository { public class StudentRepository : BaseRepository<Student>, IStudentRepository { public StudentRepository(IDbContext dbContext) : base(dbContext) { } public Student GetByName(string name) { return base._dbContext.Set<Student>().Where(x => x.Name == name).FirstOrDefault(); } } }

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

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

2. IUnitOfWork 和 Application Service 的变化

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

namespace DDD.Sample.Infrastructure.Interfaces { public interface IUnitOfWork { bool Commit(); void Rollback(); } }

由于增删改都移到 Repository 中了,因此 IUnitOfWork 的工做就很简单,只有 Commit 和 Rollback,实现也比较简单,咱们看下:

namespace DDD.Sample.Infrastructure { public class UnitOfWork : IUnitOfWork { private IDbContext _dbContext; public UnitOfWork(IDbContext dbContext) { _dbContext = dbContext; } public bool Commit() { return _dbContext.SaveChanges() > 0; } public void Rollback() { throw new NotImplementedException(); } } }

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

namespace DDD.Sample.Application { public class StudentService : IStudentService { private IUnitOfWork _unitOfWork; private IStudentRepository _studentRepository; private ITeacherRepository _teacherRepository; public StudentService(IUnitOfWork unitOfWork, IStudentRepository studentRepository, ITeacherRepository teacherRepository) { _unitOfWork = unitOfWork; _studentRepository = studentRepository; _teacherRepository = teacherRepository; } public Student Get(int id) { return _studentRepository.Get(id); } public bool Add(string name) { var student = new Student { Name = name }; var teacher = _teacherRepository.Get(1); teacher.StudentCount++; _studentRepository.Add(student); _teacherRepository.Update(teacher); return _unitOfWork.Commit(); } } }

StudentService 其实变化不大,只是将原来的 _unitOfWork 添加修改操做,改为了 _studentRepository 和 _teacherRepository,执行下 StudentService.Add 的单元测试代码,发现执行不经过,为何呢?由于 Repository 和 UnitOfWork 的 IDbContext 不是同一个对象,添加修改对象经过 Repository 注册到 IDbContext 中,最后 UnitOfWork 执行 Commit 倒是另外一个 IDbContext,因此咱们须要确保 Repository 和 UnitOfWork 共享一个 IDbContext 对象,怎么实现呢?

咱们进行改造下:

namespace DDD.Sample.Application { public class StudentService : IStudentService { private IDbContext _dbContext; private IUnitOfWork _unitOfWork; private IStudentRepository _studentRepository; private ITeacherRepository _teacherRepository; public StudentService(IDbContext dbContext) { _dbContext = dbContext; } public Student Get(int id) { _studentRepository = new StudentRepository(_dbContext); return _studentRepository.Get(id); } public bool Add(string name) { _unitOfWork = new UnitOfWork(_dbContext); _studentRepository = new StudentRepository(_dbContext); _teacherRepository = new TeacherRepository(_dbContext); var student = new Student { Name = name }; var teacher = _teacherRepository.Get(1); teacher.StudentCount++; _studentRepository.Add(student); _teacherRepository.Update(teacher); return _unitOfWork.Commit(); } } }

上面对应的测试代码执行经过,其实解决方式很简单,就是手动给 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 若是这样设计呢:

public class StudentRepository : IStudentRepository { private IQueryable<Student> _students; public StudentRepository(IDbContext dbContext) { _students = dbContext.Set<Student>(); } public Student GetByName(string name) { return _students.Where(x => x.Name == name).FirstOrDefault(); } }

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

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

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

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

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

对于本篇博文,若是你有什么问题或疑问,欢迎探讨学习。:)

相关文章
相关标签/搜索