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 架構設計過程當中,最廣泛遇到的一個問題,但也是最困惑的一個問題,好比最近兩個園友寫的博文:

對於本篇博文,若是你有什麼問題或疑問,歡迎探討學習。:)

相關文章
相關標籤/搜索