[.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
[.NET领域驱动设计实战系列]专题四:前期准备之工作单元模式(Unit Of Work)的更多相关文章
- [.NET领域驱动设计实战系列]专题五:网上书店规约模式、工作单元模式的引入以及购物车的实现
一.前言 在前面2篇博文中,我分别介绍了规约模式和工作单元模式,有了前面2篇博文的铺垫之后,下面就具体看看如何把这两种模式引入到之前的网上书店案例里. 二.规约模式的引入 在第三专题我们已经详细介绍了 ...
- [.NET领域驱动设计实战系列]专题十一:.NET 领域驱动设计实战系列总结
一.引用 其实在去年本人已经看过很多关于领域驱动设计的书籍了,包括Microsoft .NET企业级应用框架设计.领域驱动设计C# 2008实现.领域驱动设计:软件核心复杂性应对之道.实现领域驱动设计 ...
- [.NET领域驱动设计实战系列]专题一:前期准备之EF CodeFirst
一.前言 从去年已经接触领域驱动设计(Domain-Driven Design)了,当时就想自己搭建一个DDD框架,所以当时看了很多DDD方面的书,例如领域驱动模式与实战,领域驱动设计:软件核心复杂性 ...
- [.NET领域驱动设计实战系列]专题二:结合领域驱动设计的面向服务架构来搭建网上书店
一.前言 在前面专题一中,我已经介绍了我写这系列文章的初衷了.由于dax.net中的DDD框架和Byteart Retail案例并没有对其形成过程做一步步分析,而是把整个DDD的实现案例展现给我们,这 ...
- [.NET领域驱动设计实战系列]专题三:前期准备之规约模式(Specification Pattern)
一.前言 在专题二中已经应用DDD和SOA的思想简单构建了一个网上书店的网站,接下来的专题中将会对该网站补充更多的DDD的内容.本专题作为一个准备专题,因为在后面一个专题中将会网上书店中的仓储实现引入 ...
- [.NET领域驱动设计实战系列]专题十:DDD扩展内容:全面剖析CQRS模式实现
一.引言 前面介绍的所有专题都是基于经典的领域驱动实现的,然而,领域驱动除了经典的实现外,还可以基于CQRS模式来进行实现.本专题将全面剖析如何基于CQRS模式(Command Query Respo ...
- [.NET领域驱动设计实战系列]专题六:DDD实践案例:网上书店订单功能的实现
一.引言 上一专题已经为网上书店实现了购物车的功能了,在这一专题中,将继续对网上书店案例进行完善,本专题将对网上书店订单功能的实现进行介绍,现在废话不多说了,让我们来一起看看订单功能是如何实现的吧. ...
- [.NET领域驱动设计实战系列]专题七:DDD实践案例:引入事件驱动与中间件机制来实现后台管理功能
一.引言 在当前的电子商务平台中,用户下完订单之后,然后店家会在后台看到客户下的订单,然后店家可以对客户的订单进行发货操作.此时客户会在自己的订单状态看到店家已经发货.从上面的业务逻辑可以看出,当用户 ...
- [.NET领域驱动设计实战系列]专题九:DDD案例:网上书店AOP和站点地图的实现
一.引言 在前面一专题介绍到,要让缓存生效还需要实现对AOP(面向切面编程)的支持.所以本专题将介绍了网上书店案例中AOP的实现.关于AOP的概念,大家可以参考文章:http://www.cnblog ...
随机推荐
- linux 怎么完全卸载mysql数据库
在linux下开发,mysql数据库是经常用到的,对于初学者来说,在linux怎么安装卸载mysql数据库,也许可能比较痛苦,这里简单介绍下,怎么卸载msql数据库. a)查看系统中是否以rpm包安装 ...
- Getting Started With Hazelcast 读书笔记(第八章-第十章)
第八章到第十章就是一些介绍性的描述,吹的就是Hazelcast能使用在各种地方.. 第八章 -从外面看 1.Hazelcast做了一个memcache的java实现,方便py和php使用. 2.可 ...
- 设计模式:Context模式
作者:吴香伟 发表于 2014/09/12 版权声明:可以任意转载,转载时务必以超链接形式标明文章原始出处和作者信息以及版权声明 Ceph实现中使用了大量派生于Context抽象类的子类,用法简单却很 ...
- C#数据结构选择
选择一个合适的数据结构会对程序的性能有着显著的提高 线性表和链表: 1.LinkedList<T>:适合于元素数组不固定,存在大量列表的头尾添加动作场合.其它可使用List<T> ...
- Hibernate查询
HIbernate查询 使用get方法 使用get方法通过持久类名和ID号查找一个对象Stu instance = (Stu) getsession() .get("com.lovo.po. ...
- 关于HTTP session随笔
1).访问*.html的静态资源因为不会被编译为Servlet,也就不涉及session的问题. 2).当JSP页面没有显式禁止session的时候,在打开浏览器第一次请求该jsp的时候,服务器会自动 ...
- BulkyCopy .Net
It has being ages to get back to cnblogs, Career path had been changed back to .Net development in 4 ...
- html5表单验证
表单验证为终端用户检测无效的数据并标记这些错误,是一种用户体验的优化. 下面展现浏览器自带的验证功能也可在移动端中查看: HTML部分: <!DOCTYPE html> <html ...
- app启动速度
跟踪代码发现,应用启动时的白屏会持续到draw调用完成,这个过程中任何耗时操作将导致白屏时间增长. 1.adb shell am start -W -n yourpakagename/MainActi ...
- STM32 DAC的配置与使用
本博文转自:http://blog.chinaunix.net/uid-24219701-id-4101802.html STM32 的 DAC 模块(数字/模拟转换模块)是 12 位数字输入,电压输 ...