好久没写 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: