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

转载自:http: www cnblogs com xishuai 原作者:田园里的蟋蟀 好久没写 DDD 领域驱动设计相关的文章了,嘎嘎!!! 这几天
转载自:http://www.cnblogs.com/xishuai/ 
原作者:田园里的蟋蟀 

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