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。

 

具體實現請參考:https://git.oschina.net/richieyangs/BookLibrary.git

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章