在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。