好久沒寫 DDD 領域驅動設計相關的文章了,嘎嘎!!!
這幾天在開發一個新的項目,雖然不是基于領域驅動設計的,但我想把 DDD 架構設計的一些東西運用在上面,但發現了很多問題,這些在之前的短消息項目中也有,比如我一直想重構短消息 Repository 實現的一些東西,但之前完全沒有頭緒,因為內部的實現錯綜復雜,牽一發而動全身,不知道從哪下手。
正好這次新項目的開發,讓我一步一步代碼設計,所以之前疑惑的問題,可以很清晰的分析并解決,解決問題的過程最終形成了一個 DDD 框架示例,大家可以參考下:
開源地址:https://github.com/yuezhongxin/DDD.Sample
1. 一點一滴-疑惑出現
疑惑就是 Repository 和 IUnitOfWork,以及 Application Service 的調用,這樣說可能很籠統,其實就是這三者如何更好的結合使用?并且讓它們各司其職,發揮出自己的最大作用,下面我舉個例子,可能大家更好理解一些。
首先,關于 IUnitOfWork 的定義實現,網上我搜了很多,很多都太一樣,比如有人這樣定義:
public interface IUnitOfWork
{
IQueryable<TEntity> Set<TEntity>() where TEntity : class;
TEntity Add<TEntity>(TEntity entity) where TEntity : class;
TEntity Attach<TEntity>(TEntity entity) where TEntity : class;
TEntity Remove<TEntity>(TEntity entity) where TEntity : class;
void Commit();
void Rollback();
IDbContext Context { get; set; }//也有人添加這個
}
是不是感覺有點像 EF 中的 DbContext 呢?所以這也是一個疑惑點,IUnitOfWork 和 DbContext 是什么關系?比如有很多人疑惑:EF 中有 SaveChanges,為什么還有包裹一層 IUnitOfWork?這個問題之前已經討論了無數次,但這些都是紙上進行的,如果你實踐起來可能會是另一種感受。
如果 IUnitOfWork 按照上面的代碼進行設計,那 Repository 會是什么樣的呢?我們來看一下:
public class StudentRepository: IStudentRepository
{
private IUnitOfWork _unitOfWork;
public StudentRepository(IUnitOfWork unitOfWork)
{
_unitOfWork = unitOfWork;
}
public Student Get(int id)
{
return _unitOfWork.Set<Student>().Where(x => x.Id == id).FirstOrDefault();
}
public void Add(Student student)
{
return _unitOfWork.Set<Student>().Add(student);
}
//....
}
上面是 Repository 的一種設計,也有人會這樣定義:private IQueryable<Student> _students;
,然后在 StudentRepository 構造函數中進行賦值,但不管怎么設計,我們一般會將 Repository 和 IUnitOfWork 結合起來使用,這是一個重要的疑惑點:Repository 和 IUnitOfWork 真的有關系么???
另外,關于 Repository 返回 IQueryable?還是 IEnumerable?可以參考之前的一篇博文,這里我采用的是“概念上的合理”,即 Not IQueryable。
如果 Repository 按照上面的代碼進行設計,那 Application Service 會是什么樣的呢?我們來看一下:
public class StudentService : IStudentService
{
private IUnitOfWork _unitOfWork;
private IStudentRepository _studentRepository;
public StudentService(IUnitOfWork unitOfWork,
IStudentRepository studentRepository)
{
_unitOfWork = unitOfWork;
_studentRepository = studentRepository;
}
public Student Get(int id)
{
//return _unitOfWork.Set<Student>().Where(x => x.Id == id).FirstOrDefault();
return _studentRepository.Get(id);
}
public bool Add(Student student)
{
//_unitOfWork.Add<Student>(student);
_studentRepository.Add(student);
return _unitOfWork.Commit();
}
//....
}
看到上面的代碼,我想你應該明白到底疑惑什么了?在 StudentService 中,StudentRepository 似乎變得有些多余,因為它所做的,UnitOfWork 也都可以做,隨著項目的復雜,這樣就會造成很多的問題,比如:
- IUnitOfWork 的職責不明確。
- Repository 的職責不明確。
- Application Service 很困惑,因為它不知道該使用誰。
- Application Service 的代碼越來越亂。
- ....
2. 一步一步-分析解決
其實問題是可以進行溯源,如果一開始的設計變的很糟,那么接下來相關的其它設計,也會變的很糟,我們可以發現,上面出現問題的根源,其實就是一開始 IUnitOfWork 的設計問題,網上有關 IUnitOfWork 的設計實現,簡直五花八門,那我們應該相信誰呢?我們應該相信一開始關于 IUnitOfWork 的定義:http://martinfowler.com/eaaCatalog/unitOfWork.html
- Unit of Work:維護受業務事務影響的對象列表,并協調變化的寫入和并發問題的解決。工作單元記錄在業務事務過程中對數據庫有影響的所有變化,操作結束后,作為一種結果,工作單元了解所有需要對數據庫做的改變,統一對數據庫操作。
上面的文字定義要結合 IUnitOfWork 圖中的實現,可以更好的理解一些。
我們發現,它和我們一開始的定義差別很大,比如:IQueryable<TEntity> Set<TEntity>()
這樣的定義實現,如果結合上面的定義就不是很恰當,IUnitOfWork 是記錄業務事務過程中對象列表的改變,平常我們所說對數據的增刪改查,你可以理解為 IUnitOfWork 和增刪改有關,和查詢不太相關。
所以,我們完全按照定義,再重新實現一次 IUnitOfWork:
public interface IUnitOfWork
{
void RegisterNew<TEntity>(TEntity entity) where TEntity : class;
void RegisterDirty<TEntity>(TEntity entity) where TEntity : class;
void RegisterClean<TEntity>(TEntity entity) where TEntity : class;
void RegisterDeleted<TEntity>(TEntity entity) where TEntity : class;
bool Commit();
void Rollback();
}
你可以看到,我們完全按照定義進行實現的,甚至是接口名字都一樣,下面我們看一下 IUnitOfWork 的具體實現:
public class EfUnitOfWork : DbContext, IUnitOfWork
{
public EfUnitOfWork()
: base("name=db_school")
{ }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Student>();
modelBuilder.Entity<Teacher>();
base.OnModelCreating(modelBuilder);
}
public void RegisterNew<TEntity>(TEntity entity)
where TEntity : class
{
base.Set<TEntity>().Add(entity);
}
public void RegisterDirty<TEntity>(TEntity entity)
where TEntity : class
{
base.Entry<TEntity>(entity).State = EntityState.Modified;
}
public void RegisterClean<TEntity>(TEntity entity)
where TEntity : class
{
base.Entry<TEntity>(entity).State = EntityState.Unchanged;
}
public void RegisterDeleted<TEntity>(TEntity entity)
where TEntity : class
{
base.Set<TEntity>().Remove(entity);
}
public bool Commit()
{
return base.SaveChanges() > 0;
}
public void Rollback()
{
throw new NotImplementedException();
}
}
EfUnitOfWork 繼承 DbContext 和 IUnitOfWork,使用 EF 作為對象管理和持久化,這樣實現好像沒有什么問題,我們一般也是這樣做的,其實,這是一個坑,我們后面會講到,另外,之前說到 EF 中有 SaveChanges,為什么還有包裹一層 IUnitOfWork?其實就是說的上面代碼,因為所有的 IUnitOfWork 的實現,我們都是使用的 EF,既然如此,為啥不把 IUnitOfWork 這個空殼拿掉呢?有人會說了,IUnitOfWork 是 DDD 中的概念,巴拉巴拉,不能拿掉,要不然就不是 DDD 了呢?
上面的問題先放在這個,如果 EfUnitOfWork 這樣實現 IUnitOfWork,那 Repository 會怎樣?看下面的代碼:
public class StudentRepository: IStudentRepository
{
private IQueryable<Student> _students;
public StudentRepository(IUnitOfWork unitOfWork)
{
_students = unitOfWork.Set<Student>();//沒有了之前的 Set<TEntity>(),咋辦啊???
}
public Student Get(int id)
{
return _students.Where(x => x.Id == id).FirstOrDefault();
}
//....
}
代碼進行到這,突然進行不下去了,咋辦呢?如果你回過頭去修改 IUnitOfWork 的接口,比如增加 Set<TEntity>()
,這時候將又回到開始的問題,一切將前功盡棄,這么解決呢?這時候要停下來,思考 Repository 和 IUnitOfWork 的關系,也就是之前提到的,它們倆有關系么???
IUnitOfWork 的定義上面說過了,我們再看下 Repository 的定義:
- Repository:協調領域和數據映射層,利用類似于集合的接口來訪問領域對象。
重點在于訪問,Repository 是用來訪問領域對象的,所以,之前我們在 IRepository 中定義 Add、Update、Renove 等等接口,我覺得這些不是很恰當,因為對象列表的更改,我們可以用 IUnitOfWork 記錄和實現,Repository 和 IUnitOfWork 應該是平級的概念,如果在 Repository 中去使用 IUnitOfWork,就有點違背其定義了。
IUnitOfWork 有其 EfUnitOfWork 的實現,難道我們還要搞一個 IRepository 對應的 EfRepository 實現?很顯然,如果這樣設計是非常冗余的,這時候,你是不是想到了我們還沒有提到的 IDbContext 呢???沒錯就是它,讓 UnitOfWork 和 Repository 都依賴于 IDbContext,而不是依賴于具體的 EF,這樣也就沒有了之前一直提到的 UnitOfWork 和 EF 的問題,具體怎么做呢?我們得先定義 IDbContext:
public interface IDbContext
{
DbSet<TEntity> Set<TEntity>()
where TEntity : class;
DbEntityEntry<TEntity> Entry<TEntity>(TEntity entity)
where TEntity : class;
int SaveChanges();
}
IDbContext 的作用,就是提供對象列表的一切操作接口,接下來實現一個 SchoolDbContext:
public class SchoolDbContext : DbContext, IDbContext
{
public SchoolDbContext()
: base("name=db_school")
{ }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Student>();
modelBuilder.Entity<Teacher>();
base.OnModelCreating(modelBuilder);
}
}
SchoolDbContext 有點像我們之前的 EfUnitOfWork,但它和 IUnitOfWork 沒有任何關系,它的作用就是提供具體的對象持久化和訪問,我們一般會放在 Infrastructure 層。另外,可以看到 SchoolDbContext 似乎并沒有實現 IDbContext 的接口,為什么呢?因為我們繼承了 DbContext,這些接口都在 DbContext 中進行進行實現了,你可以按 F12 進行查看。
接下來我們要對 EfUnitOfWork 進行改造了,IUnitOfWork 和 EF 已經沒有半毛錢關系了,所以我們命名直接去掉 Ef,具體實現代碼:
public class UnitOfWork : IUnitOfWork
{
private IDbContext _dbContext;
public UnitOfWork(IDbContext dbContext)
{
_dbContext = dbContext;
}
public void RegisterNew<TEntity>(TEntity entity)
where TEntity : class
{
_dbContext.Set<TEntity>().Add(entity);
}
public void RegisterDirty<TEntity>(TEntity entity)
where TEntity : class
{
_dbContext.Entry<TEntity>(entity).State = EntityState.Modified;
}
public void RegisterClean<TEntity>(TEntity entity)
where TEntity : class
{
_dbContext.Entry<TEntity>(entity).State = EntityState.Unchanged;
}
public void RegisterDeleted<TEntity>(TEntity entity)
where TEntity : class
{
_dbContext.Set<TEntity>().Remove(entity);
}
public bool Commit()
{
return _dbContext.SaveChanges() > 0;
}
public void Rollback()
{
throw new NotImplementedException();
}
}
UnitOfWork 脫離了 EF, 是不是有種小清新的感覺?UnitOfWork 依賴于 IDbContext,所以,UnitOfWork 并不關心用哪種具體技術進行實現,你只需要給我對象訪問和持久化接口就可以了,下面再看一下之前進行不下去的 Repository:
public class StudentRepository: IStudentRepository
{
private IQueryable<Student> _students;
public StudentRepository(IDbContext dbContext)
{
_students = dbContext.Set<Student>();
}
public Student Get(int id)
{
return _students.Where(x => x.Id == id).FirstOrDefault();
}
}
Repository 脫離了 IUnitOfWork,也有種小清新的感覺,Repository 的定義中說到,訪問領域對象的集合,這個我們可以從 IDbContext 中進行操作,再來看一下 Application Service 中的代碼:
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++;
_unitOfWork.RegisterNew(student);
_unitOfWork.RegisterDirty(teacher);
return _unitOfWork.Commit();
}
}
需要仔細看下 Add 中的方法,為了測試同一上下文中不同實體的操作,所以,我后面又增加了 Teacher 實體,因為這個方法比較有代表性,我大致分解下過程:
- 通過 _teacherRepository 獲取 Teacher 對象,注意這個操作通過 TeacherRepository 中的 IDbContext 完成。
- teacher 對象的學生數量 +1。
- 通過 _unitOfWork.RegisterNew 標識添加實體對象 student。
- 通過 _unitOfWork.RegisterDirty 標識修改實體對象 teacher。
- 通過 _unitOfWork.Commit 提交更改。
這個方法測試的主要目的是,通過 Repository 獲取對象,并進行相應修改,然后用 UnitOfWork 提交修改,另外,在這個過程中,也會有其它對象的一些操作,測試是可行的,可以很好的避免之前修改 Student 需要通過 StudentRepository,修改 Teacher 需要通過 TeacherRepository,然后 Commit 兩次,別問我為什么?因為我之前就這樣干過。。。
最后的最后,附上測試代碼:
public class StudentServiceTest
{
private IStudentService _studentService;
public StudentServiceTest()
{
var container = new UnityContainer();
container.RegisterType<IDbContext, SchoolDbContext>();
container.RegisterType<IUnitOfWork, UnitOfWork>();
container.RegisterType<IStudentRepository, StudentRepository>();
container.RegisterType<ITeacherRepository, TeacherRepository>();
container.RegisterType<IStudentService, StudentService>();
_studentService = container.Resolve<IStudentService>();
}
[Fact]
public void GetByIdTest()
{
var student = _studentService.Get(1);
Assert.NotNull(student);
}
[Fact]
public void AddTest()
{
var result = _studentService.Add("xishuai");
Assert.True(result);
}
}
文章列表