Unit Of Work的设计
在DDD开发过程中,一个良好的Uow设计必不可少,我心目中的Uow设计应该具备以下几点:
1、有着良好的抽象,有着恰如其分的命名;
2、能够应付不同的组件,比如你的系统中可能会存在EfUnitOfWork、RedisUnitOfWork;
3、易于使用,不用开发者显示调用。Uow在一个用户请求开始到结束能够自动包裹在业务逻辑外边;
在阅读了Abp的源码后我感觉Abp中的Uow正好符合我这几点要求,但是其实现稍有点复杂,例如在EfUnitOfWork中加入了DynamicFilters,EfUnitOfWork支持多DbContext,这些复杂性导致EfUnitOfWork类变得有点臃肿。所以我在实例代码中移除了这些设计。
一、业务抽象
一个使用Uow的典型代码片段如下:
using (var uow = UowManager.Begin()) { //....logic uow.Commit(); }
从这段代码中我们基本可以分析出下面的Uow抽象。
1、IUnitOfWorkManager
从上面的代码片段可以看出,此Manager应该具有一个Begin()方法,返回IDisposable类型。
2、IUnitOfWorkCompleteHandle
IUnitOfWorkManager的Begin()方法返回的IDisposable类型应该具有Commit()的能力,该抽象用于对IUnitOfWork的提交和释放资源。
3、IUnitOfWork
从这个代码片段中我们并没有看到IUnitOfWork这个抽象,原因在于IUnitOfWorkManager隐藏了具体的IUnitOfWork,IUnitOfWorkManager.Begin()实现中实际上是具体的IUnitOfWork.Begin()调用。
4、ICurrentUnitOfWorkProvider
针对用户在一次上下文中的请求,具有唯一的一个IUnitOfWork,因此可以在任意时刻通过ICurrentUnitOfWorkProvider来读取当前上下文的IUnitOfWork。
通过接口命名描述就能理清整个Uow设计思路:
二、EfUnitOfWork
EfUnitOfWork的Begin体现在对TransactionScope的调用、Commit体现在对dbContext.SaveChanges()的调用
public void Begin(UnitOfWorkOptions options) { _transactionScope = new TransactionScope( options.TransactionScopeOption.GetValueOrDefault(_defaultUnitOfWorkOptions.TransactionScopeOption), new TransactionOptions() { IsolationLevel = options.IsolationLevel.GetValueOrDefault(_defaultUnitOfWorkOptions.IsolationLevel), Timeout = options.Timeout.GetValueOrDefault(_defaultUnitOfWorkOptions.Timeout) }); }
public void Complete() { try { _dbContext.SaveChanges(); _transactionScope?.Complete(); _completed?.Invoke(); } catch { _failed?.Invoke(); throw; } }
三、通过Castle实现Aop,将Uow包裹ApplicationService层
定义UnitOfWorkInterceptor,该拦截器表现为要对现有的一个方法包裹UnitOfWork实现。
private void PerformUow(IInvocation invocation, UnitOfWorkOptions options) { using (var uow = _unitOfWorkManager.Begin(options)) { invocation.Proceed(); uow.Complete(); } }
四、缺陷
该方案是一个很优秀的UnitOfWork设计,不过当我们在使用DDD模型时如果引申出了领域事件,该方案则不够理想。当领域模型未能够正确持久化时则不应该发布领域事件。针对这一要求我有两个想法:
1、将发布领域事件注册在UnitOfWorkManager的Completed阶段,确保EfUnitOfWork正确执行后再发布领域事件;
2、抽象出EfUnitOfWorkParticipant、IServiceBusParticipant。在Commit阶段分别Commit这两个participant,有一个失败则Rollback所有的participant。