[.NET领域驱动设计实战系列]专题四:前期准备之工作单元模式(Unit Of Work)
一、前言
在前一专题中介绍了规约模式的实现,然后在仓储实现中,经常会涉及工作单元模式的实现。然而,在我的网上书店案例中也将引入工作单元模式,所以本专题将详细介绍下该模式,为后面案例的实现做一个铺垫。
二、什么是工作单元模式(Unit Of Work)
工作单元模式:用来维护一个已经被业务事务修改(包括添加、修改或更新)的业务对象列表。工作单元模式复制协调这些修改的持久化工作以及所有标记的并发问题。采用工作单元模式带来的好处是能够保证数据的完整性。如果在持久化一系列业务对象的过程中出现问题,则将所有的修改回滚,以保证数据始终处于有效状态。
简单来说,工作单元模式就是把业务对象的持久化由工作单元实现类进行统一管理。而不想之前那样,分布在每个具体的仓储类中,这样就可以达到一系列业务对象的统一管理,不至于在每个业务对象中出现统一的提交和回滚业务逻辑,实现代码最大化重用。
三、工作单元模式的实现
从工作单元模式的定义可以看出,工作单元需要保存被业务事务修改的业务对象列表,则必须定义3个集合,分别是添加、修改和更新集合,然而如果使用EF的话,则不需要了,因为DbContext.DbSet<T>就可以代替这三个集合。这里为了演示,我们并没有引入EF,所以我们实现中定义了3个集合来保存被修改的业务对象。既然要在工作单元类中进行统一管理,则我们就需要在工作单元类中定义一个Commit方法,该方法的实现就是去遍历这三个集合的对象,对它们进行统一提交,如果其中一个失败,则进行数据回滚。根据面向接口编程原则,我们需要定义一个工作单元接口,即IUnitOfWork接口。经过上面的分析,再结合下面具体的实现来理解,工作单元模式的实现也就更加清晰了,下面让我们一起去实现下工作单元模式。
第一步:我们需要定义我们的领域层。
这里以银行账号之间转账业务作为背景,自然涉及到银行账号业务对象。并且领域层同时包括仓储接口的定义和领域服务。
领域服务指的是:如果有些方法涉及多个实体或聚合的交互,此时就应该把这段逻辑放到领域服务中,领域服务只有方法没有属性,也就是没有状态的。在银行转账业务中,账户之间的转账操作就适合作为领域服务来提供,因为转账操作涉及多个聚合间的交互,需要从一个账户扣钱和另一个账户加钱。所以在领域层还需要定义账户转账服务。经过这样的分析,则领域层的实现如下所示:
// 账号仓储接口 public interface IAccountRepository { void Save(Account account); void Add(Account account); void Remove(Account account); } // 账号实体类 public class Account : IAggregateRoot { public decimal Balance { get; set; } public System.Guid Id { get; set; } public Account() { Id = Guid.NewGuid(); } } // 账号转账领域服务类 public class AccountService { private readonly IAccountRepository _productRepository; private readonly IUnitOfWork _unitOfWork; public AccountService(IAccountRepository productRepository, IUnitOfWork unitOfWork) { _productRepository = productRepository; _unitOfWork = unitOfWork; } public void Transfer(Account from, Account to, decimal amount) { if (from.Balance >= amount) { from.Balance -= amount; to.Balance += amount; _productRepository.Save(from); _productRepository.Save(to); _unitOfWork.Commit(); } } }
第二步:构建基础设施层
我们一般把工作单元模式的实现放在基础设施层,因为工作单元模式属于一种技术支持。根据上面工作单元模式的分析,我们首先定义IUnitOfWork接口,接着定义它的实现。因为这里没有引入EF,所以具体的实体的持久化还是调用具体的仓储来实现持久化的,所以还需要定义一个IUnitOfWorkRepository接口。则基础设施层的实现如下所示:
// 工作单元接口 public interface IUnitOfWork { void RegisterAmended(IAggregateRoot entity, IUnitOfWorkRepository unitofWorkRepository); void RegisterNew(IAggregateRoot entity, IUnitOfWorkRepository unitofWorkRepository); void RegisterRemoved(IAggregateRoot entity, IUnitOfWorkRepository unitofWorkRepository); void Commit(); } // 工作单元实现 public class UnitOfWork : IUnitOfWork { // 引入了EF就不需要额外定义三个列表了,因为EF框架中包含的DbContext.DbSet<T>可以记录这3个列表 // 然而在ByteartRetail案例中,也定义这3个列表,但其没有被真真使用到 private readonly Dictionary<IAggregateRoot, IUnitOfWorkRepository> _addedEntities; private readonly Dictionary<IAggregateRoot, IUnitOfWorkRepository> _changedEntities; private readonly Dictionary<IAggregateRoot, IUnitOfWorkRepository> _deletedEntities; public UnitOfWork() { _addedEntities = new Dictionary<IAggregateRoot, IUnitOfWorkRepository>(); _changedEntities = new Dictionary<IAggregateRoot, IUnitOfWorkRepository>(); _deletedEntities = new Dictionary<IAggregateRoot, IUnitOfWorkRepository>(); } // 将业务对象实体添加到内部列表中,真正完成实体持久化操作的还是由具体的仓储类去完成 public void RegisterAmended(IAggregateRoot entity, IUnitOfWorkRepository unitofWorkRepository) { if (!_changedEntities.ContainsKey(entity)) { _changedEntities.Add(entity, unitofWorkRepository); } } public void RegisterNew(IAggregateRoot entity, IUnitOfWorkRepository unitofWorkRepository) { if (!_addedEntities.ContainsKey(entity)) { _addedEntities.Add(entity, unitofWorkRepository); }; } public void RegisterRemoved(IAggregateRoot entity, IUnitOfWorkRepository unitofWorkRepository) { if (!_deletedEntities.ContainsKey(entity)) { _deletedEntities.Add(entity, unitofWorkRepository); } } protected void ClearRegisterations() { _addedEntities.Clear(); _changedEntities.Clear(); _deletedEntities.Clear(); } // 对内部列表进行统一提交 // 引入EF后,提交的实现有点不同,它具体的持久化只需要调用DbContext.SaveChanges方法来完成 // 则具体的仓储接口不需要实现IUnitOfWorkRepository接口,则自然不存在IUnitOfWorkRepository接口的定义 public void Commit() { // 事务范围 using (var scope = new TransactionScope()) { // 分别调用具体的仓储对象的持久化逻辑来对业务对象进行持久化 foreach (var entity in this._addedEntities.Keys) { this._addedEntities[entity].PersistCreationOf(entity); } foreach (var entity in this._changedEntities.Keys) { this._changedEntities[entity].PersistUpdateOf(entity); } foreach (var entity in this._deletedEntities.Keys) { this._deletedEntities[entity].PersistDeletionOf(entity); } scope.Complete(); } // 清楚内存中对象 ClearRegisterations(); } } public interface IUnitOfWorkRepository { Hashtable AccountList { get; } void PersistCreationOf(IAggregateRoot entity); void PersistUpdateOf(IAggregateRoot entity); void PersistDeletionOf(IAggregateRoot entity); } public interface IAggregateRoot { Guid Id { get; } }
第三步:实现仓储层。
仓储的实现在之前也说过,它其实可以放在基础设施层里,但一般总将其放在一个单独层进行实现。所以这里也就放在一个单独层进行实现。这里仓储实现只有一个类,即对IAccountRepository接口的实现。具体的实现代码如下所示:
public class AccountRepository : IAccountRepository, IUnitOfWorkRepository { private readonly IUnitOfWork _unitOfWork; public AccountRepository(IUnitOfWork unitOfWork) { _unitOfWork = unitOfWork; AccountList = new Hashtable(); } public Hashtable AccountList { get; set; } #region IAccountRepository Members public void Save(Account account) { _unitOfWork.RegisterAmended(account, this); } public void Add(Account account) { _unitOfWork.RegisterNew(account, this); } public void Remove(Account account) { _unitOfWork.RegisterRemoved(account, this); } #endregion #region IUnitOfWorkRepository Members public void PersistUpdateOf(IAggregateRoot entity) { // ADO.net code to update the entity... // 这里为了演示,只它持久化到内存中 if (AccountList.ContainsKey(entity.Id)) { AccountList[entity.Id] = entity; } } public void PersistCreationOf(IAggregateRoot entity) { // ADO.net code to Add the entity... // 这里为了演示,只它持久化到内存中 AccountList.Add(entity.Id, entity); } public void PersistDeletionOf(IAggregateRoot entity) { // ADO.net code to delete the entity... // 这里为了演示,只它持久化到内存中 if (AccountList.ContainsKey(entity.Id)) { AccountList.Remove(entity.Id); } } #endregion }
这样,就完成了工作单元模式模式的实现和应用了,工作单元模式的实现存在于基础设施层,其他层的构建主要为了演示,下面通过一个测试项目来进行测试下工作单元的使用。具体项目的引入将会在下一个专题中介绍,下一个专题将引入工作单元模式和规约模式的实现。具体的测试项目的代码如下所示:
[TestClass] public class AccountRepositoryTests { [TestMethod] public void AccountRepository_Delegates_Changes_To_The_Unit_Of_Work_Instance() { var accountToBeAmended = new Account(); var accountToBeRemoved = new Account(); var accountToBeAdded = new Account(); // 需要引入Moq Mock框架 var unitOfWorkMockery = new Mock<IUnitOfWork>(); var accountRepository = new AccountRepository(unitOfWorkMockery.Object); unitOfWorkMockery.Setup(uow => uow.RegisterAmended(accountToBeAmended, accountRepository)); unitOfWorkMockery.Setup(uow => uow.RegisterNew(accountToBeAdded, accountRepository)); unitOfWorkMockery.Setup(uow => uow.RegisterRemoved(accountToBeRemoved, accountRepository)); accountRepository.Add(accountToBeAdded); accountRepository.Save(accountToBeAmended); accountRepository.Remove(accountToBeRemoved); unitOfWorkMockery.VerifyAll(); } }
四、总结
到这里,本专题的内容就介绍完了,上一专题和这一专题都是一个前期准备的专题,主要是为网上书店案例引入这2个模式的实现做一个铺垫,为了让大家对知识点分开吸收,然后再通过后面一专题的应用来加深理解这2个模式的应用。
本专题的所有源码下载:UnitOfWorkDemo.zip