日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

[Abp vNext 源码分析] - 4. 工作单元

發布時間:2023/12/4 编程问答 31 豆豆
生活随笔 收集整理的這篇文章主要介紹了 [Abp vNext 源码分析] - 4. 工作单元 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

一、簡要說明

統一工作單元是一個比較重要的基礎設施組件,它負責管理整個業務流程當中涉及到的數據庫事務,一旦某個環節出現異常自動進行回滾處理。

在 ABP vNext 框架當中,工作單元被獨立出來作為一個單獨的模塊(Volo.Abp.Uow)。你可以根據自己的需要,來決定是否使用統一工作單元。

二、源碼分析

整個 Volo.Abp.Uow 項目的結構如下,從下圖還是可以看到我們的老朋友?IUnitOfWorkManager?和?IUnitOfWork?,不過也多了一些新東西??匆粋€模塊的功能,首先從它的?Module?入手,我們先看一下?AbpUnitofWorkModule?里面的實現。

2.1 工作單元的初始模塊

打開?AbpUnitOfWorkModule?里面的代碼,發現還是有點失望,里面就一個服務注冊完成事件。

public override void PreConfigureServices(ServiceConfigurationContext context){ context.Services.OnRegistred(UnitOfWorkInterceptorRegistrar.RegisterIfNeeded);}

這里的結構和之前看的?審計日志?模塊類似,就是注冊攔截器的作用,沒有其他特別的操作。

2.1.1 攔截器注冊

繼續跟進代碼,其實現是通過?UnitOfWorkHelper?來確定哪些類型應該集成?UnitOfWork?組件。

public static void RegisterIfNeeded(IOnServiceRegistredContext context){ if (UnitOfWorkHelper.IsUnitOfWorkType(context.ImplementationType.GetTypeInfo())) { context.Interceptors.TryAdd<UnitOfWorkInterceptor>(); }}

繼續分析?UnitOfWorkHelper?內部的代碼,第一種情況則是實現類型 (implementationType) 或類型的任一方法標注了?UnitOfWork?特性的話,都會為其注冊工作單元攔截器。

第二種情況則是 ABP vNext 為我們提供了一個新的?IUnitOfWorkEnabled?標識接口。只要繼承了該接口的實現,都會被視為需要工作單元組件,會在系統啟動的時候,自動為它綁定攔截器。

public static bool IsUnitOfWorkType(TypeInfo implementationType){ if (HasUnitOfWorkAttribute(implementationType) || AnyMethodHasUnitOfWorkAttribute(implementationType)) { return true; } if (typeof(IUnitOfWorkEnabled).GetTypeInfo().IsAssignableFrom(implementationType)) { return true; } return false;}

2.2 新的接口與抽象

在 ABP vNext 當中,將一些?職責?從原有的工作單元進行了?分離。抽象出了?IDatabaseApi?、ISupportsRollback、ITransactionApi?這三個接口,這三個接口分別提供了不同的功能和職責。

2.2.1 數據庫統一訪問接口

這里以?IDatabaseApi?為例,它是提供了一個?數據庫提供者(Database Provider)?的抽象概念,在 ABP vNext 里面,是將 EFCore 作為數據庫概念來進行抽象的。(因為后續 MongoDb 與 MemoryDb 與其同級)

你可以看作是 EF Core 的 Provider ,在 EF Core 里面我們可以實現不同的 Provider ,來讓 EF Core 支持訪問不同的數據庫。

而 ABP vNext 這么做的意圖就是提供一個統一的數據庫訪問 API,如何理解呢?這里以?EFCoreDatabaseApi<TDbContext>?為例,你查看它的實現會發現它繼承并實現了?ISupportsSavingChanges?,也就是說?EFCoreDatabaseApi<TDbContext>?支持?SaveChanges?操作來持久化數據更新與修改。

public class EfCoreDatabaseApi<TDbContext> : IDatabaseApi, ISupportsSavingChanges where TDbContext : IEfCoreDbContext{ public TDbContext DbContext { get; } public EfCoreDatabaseApi(TDbContext dbContext) { DbContext = dbContext; } public Task SaveChangesAsync(CancellationToken cancellationToken = default) { return DbContext.SaveChangesAsync(cancellationToken); } public void SaveChanges() { DbContext.SaveChanges(); }}

也就是說 SaveChanges 這個操作,是 EFCore 這個 DatabaseApi 提供了一種特殊操作,是該類型數據庫的一種特殊接口。

如果針對于某些特殊的數據庫,例如 InfluxDb 等有一些特殊的 Api 操作時,就可以通過一個 DatabaseApi 類型進行處理。

2.2.2 數據庫事務接口

通過最開始的項目結構會發現一個?ITransactionApi?接口,這個接口只定義了一個?事務提交操作(Commit),并提供了異步方法的定義。

public interface ITransactionApi : IDisposable{ void Commit(); Task CommitAsync();}

跳轉到其典型實現?EfCoreTransactionApi?當中,可以看到該類型還實現了?ISupportsRollback?接口。通過這個接口的名字,我們大概就知道它的作用,就是提供了回滾方法的定義。如果某個數據庫支持回滾操作,那么就可以為其實現該接口。

其實這里按照語義,你也可以將它放在?EfCoreDatabaseApi<TDbContext>?進行實現,因為回滾也是數據庫提供的 API 之一,只是在 ABP vNext 里面又將其歸為事務接口進行處理了。

這里就不再詳細贅述該類型的具體實現,后續會在單獨的 EF Core 章節進行說明。

2.3 工作單元的原理與實現

在 ABP vNext 框架當中的工作單元實現,與原來 ABP 框架有一些不一樣。

2.3.1 內部工作單元 (子工作單元)

首先說內部工作單元的定義,現在是有一個新的?ChildUnitOfWork?類型作為?子工作單元。子工作單元本身并不會產生實際的業務邏輯操作,基本所有邏輯都是調用?UnitOfWork?的方法。

internal class ChildUnitOfWork : IUnitOfWork{ public Guid Id => _parent.Id; public IUnitOfWorkOptions Options => _parent.Options; public IUnitOfWork Outer => _parent.Outer; public bool IsReserved => _parent.IsReserved; public bool IsDisposed => _parent.IsDisposed; public bool IsCompleted => _parent.IsCompleted; public string ReservationName => _parent.ReservationName; public event EventHandler<UnitOfWorkFailedEventArgs> Failed; public event EventHandler<UnitOfWorkEventArgs> Disposed; public IServiceProvider ServiceProvider => _parent.ServiceProvider; private readonly IUnitOfWork _parent; public ChildUnitOfWork([NotNull] IUnitOfWork parent) { Check.NotNull(parent, nameof(parent)); _parent = parent; _parent.Failed += (sender, args) => { Failed.InvokeSafely(sender, args); }; _parent.Disposed += (sender, args) => { Disposed.InvokeSafely(sender, args); }; } public void SetOuter(IUnitOfWork outer) { _parent.SetOuter(outer); } public void Initialize(UnitOfWorkOptions options) { _parent.Initialize(options); } public void Reserve(string reservationName) { _parent.Reserve(reservationName); } public void SaveChanges() { _parent.SaveChanges(); } public Task SaveChangesAsync(CancellationToken cancellationToken = default) { return _parent.SaveChangesAsync(cancellationToken); } public void Complete() { } public Task CompleteAsync(CancellationToken cancellationToken = default) { return Task.CompletedTask; } public void Rollback() { _parent.Rollback(); } public Task RollbackAsync(CancellationToken cancellationToken = default) { return _parent.RollbackAsync(cancellationToken); } public void OnCompleted(Func<Task> handler) { _parent.OnCompleted(handler); } public IDatabaseApi FindDatabaseApi(string key) { return _parent.FindDatabaseApi(key); } public void AddDatabaseApi(string key, IDatabaseApi api) { _parent.AddDatabaseApi(key, api); } public IDatabaseApi GetOrAddDatabaseApi(string key, Func<IDatabaseApi> factory) { return _parent.GetOrAddDatabaseApi(key, factory); } public ITransactionApi FindTransactionApi(string key) { return _parent.FindTransactionApi(key); } public void AddTransactionApi(string key, ITransactionApi api) { _parent.AddTransactionApi(key, api); } public ITransactionApi GetOrAddTransactionApi(string key, Func<ITransactionApi> factory) { return _parent.GetOrAddTransactionApi(key, factory); } public void Dispose() { } public override string ToString() { return $"[UnitOfWork {Id}]"; }}

雖然基本上所有方法的實現,都是調用的實際工作單元實例。但是有兩個方法?ChildUnitOfWork?是空實現的,那就是?Complete()?和?Dispose()?方法。

這兩個方法一旦在內部工作單元調用了,就會導致?事務被提前提交,所以這里是兩個空實現。

下面就是上述邏輯的偽代碼:

using(var transactioinUow = uowMgr.Begin()){ using(var childUow1 = uowMgr.Begin()) { using(var childUow2 = uowMgr.Begin()) { childUow2.Complete(); } childUow1.Complete(); } transactioinUow.Complete();}

以上結構一旦某個內部工作單元拋出了異常,到會導致最外層帶事務的工作單元無法調用?Complete()方法,也就能夠保證我們的?數據一致性。

2.3.2 外部工作單元

首先我們查看?UnitOfWork?類型和?IUnitOfWork?的定義和屬性,可以獲得以下信息。

  • 每個工作單元是瞬時對象,因為它繼承了?ITransientDependency?接口。

  • 每個工作單元都會有一個?Guid?作為其唯一標識信息。

  • 每個工作單元擁有一個?IUnitOfWorkOptions?來說明它的配置信息。

    這里的配置信息主要指一個工作單元在執行時的?超時時間是否包含一個事務,以及它的?事務隔離級別(如果是事務性的工作單元的話)。

  • 每個工作單元存儲了?IDatabaseApi?與?ITransactionApi?的集合,并提供了訪問/存儲接口。

  • 提供了兩個操作事件?Failed?與?Disposed。

    這兩個事件分別在工作單元執行失敗以及被釋放時(調用?Dispose()?方法)觸發,開發人員可以掛載這兩個事件提供自己的處理邏輯。

  • 工作單元還提供了一個工作單元完成事件組。

    用于開發人員在工作單元完成時(調用Complete()?方法)掛載自己的處理事件,因為是?List<Func<Task>>?所以你可以指定多個,它們都會在調用?Complete()?方法之后執行,例如如下代碼:

    using (var uow = _unitOfWorkManager.Begin()){ uow.OnCompleted(async () => completed = true); uow.OnCompleted(async()=>Console.WriteLine("Hello ABP vNext")); uow.Complete();}
  • 以上信息是我們查看了?UnitOfWork?的屬性與接口能夠直接得出的結論,接下來我會根據一個工作單元的生命周期來說明一遍工作單元的實現。

    一個工作單元的的構造是通過工作單元管理器實現的(IUnitOfWorkManager),通過它的?Begin()?方法我們會獲得一個工作單元,至于這個工作單元是外部工作單元還是內部工作單元,取決于開發人員傳入的參數。

    public IUnitOfWork Begin(UnitOfWorkOptions options, bool requiresNew = false){ Check.NotNull(options, nameof(options)); var currentUow = Current; if (currentUow != null && !requiresNew) { return new ChildUnitOfWork(currentUow); } var unitOfWork = CreateNewUnitOfWork(); unitOfWork.Initialize(options); return unitOfWork;}

    這里需要注意的就是創建新的外部工作單元方法,它這里就使用了 IoC 容器提供的?Scope?生命周期,并且在創建之后會將最外部的工作單元設置為最新創建的工作單元實例。

    private IUnitOfWork CreateNewUnitOfWork(){ var scope = _serviceProvider.CreateScope(); try { var outerUow = _ambientUnitOfWork.UnitOfWork; var unitOfWork = scope.ServiceProvider.GetRequiredService<IUnitOfWork>(); unitOfWork.SetOuter(outerUow); _ambientUnitOfWork.SetUnitOfWork(unitOfWork); unitOfWork.Disposed += (sender, args) => { _ambientUnitOfWork.SetUnitOfWork(outerUow); scope.Dispose(); }; return unitOfWork; } catch { scope.Dispose(); throw; }}

    上述描述可能會有些抽象,結合下面這兩幅圖可能會幫助你的理解。

    我們可以在任何地方注入?IAmbientUnitOfWork?來獲取當前活動的工作單元,關于?IAmbientUnitOfWork?與?IUnitOfWorkAccessor?的默認實現,都是使用的?AmbientUnitOfWork。

    在該類型的內部,通過?AsyncLocal<IUnitOfWork>?來確保在不同的?異步上下文切換?過程中,其值是正確且統一的。

    構造了一個外部工作單元之后,我們在倉儲等地方進行數據庫操作。操作完成之后,我們需要調用?Complete()?方法來說明我們的操作已經完成了。如果你沒有調用?Complete()?方法,那么工作單元在被釋放的時候,就會產生異常,并觸發?Failed?事件。

    public virtual void Dispose(){ if (IsDisposed) { return; } IsDisposed = true; DisposeTransactions(); if (!IsCompleted || _exception != null) { OnFailed(); } OnDisposed();}

    所以,我們在手動使用工作單元管理器構造工作單元的時候,一定要注意調用?Complete()?方法。

    既然?Complete()?方法這么重要,它內部究竟做了什么事情呢?下面我們就來看一下。

    public virtual void Complete(){ if (_isRolledback) { return; } PreventMultipleComplete(); try { _isCompleting = true; SaveChanges(); CommitTransactions(); IsCompleted = true; OnCompleted(); } catch (Exception ex) { _exception = ex; throw; }}public virtual void SaveChanges(){ foreach (var databaseApi in _databaseApis.Values) { (databaseApi as ISupportsSavingChanges)?.SaveChanges(); }}protected virtual void CommitTransactions(){ foreach (var transaction in _transactionApis.Values) { transaction.Commit(); }}protected virtual void RollbackAll(){ foreach (var databaseApi in _databaseApis.Values) { try { (databaseApi as ISupportsRollback)?.Rollback(); } catch { } } foreach (var transactionApi in _transactionApis.Values) { try { (transactionApi as ISupportsRollback)?.Rollback(); } catch { } }}

    這里可以看到,ABP vNext 完全剝離了具體事務或者回滾的實現方法,都是移動到具體的模塊進行實現的,也就是說在調用了?Complete()?方法之后,我們的事務就會被提交了。

    本小節從創建、提交、釋放這三個生命周期講解了工作單元的原理和實現,關于具體的事務和回滾實現,我會放在下一篇文章進行說明,這里就不再贅述了。

    為什么工作單元常常配合?using 語句塊?使用,就是因為在提交工作單元之后,就可以自動調用?Dispose()?方法,對工作單元的狀態進行校驗,而不需要我們手動處理。

    using(var uowA = _uowMgr.Begion()){ uowA.Complete();}

    2.3.3 保留工作單元

    在 ABP vNext 里面,工作單元有了一個新的動作/屬性,叫做?是否保留(Is Reserved)。它的實現也比較簡單,指定了一個?ReservationName,然后設置?IsReserved?為?true?就完成了整個動作。

    那么它的作用是什么呢?這塊內容我會在工作單元管理器小節進行解釋。

    2.4 工作單元管理器

    工作單元管理器在工作單元的原理/實現里面已經有過了解,工作單元管理器主要負責工作單元的創建。

    這里我再挑選一個工作單元模塊的單元測試,來說明什么叫做?保留工作單元。

    [Fact]public async Task UnitOfWorkManager_Reservation_Test(){ _unitOfWorkManager.Current.ShouldBeNull(); using (var uow1 = _unitOfWorkManager.Reserve("Reservation1")) { _unitOfWorkManager.Current.ShouldBeNull(); using (var uow2 = _unitOfWorkManager.Begin()) { _unitOfWorkManager.Current.ShouldNotBeNull(); _unitOfWorkManager.Current.Id.ShouldNotBe(uow1.Id); await uow2.CompleteAsync(); } _unitOfWorkManager.Current.ShouldBeNull(); _unitOfWorkManager.BeginReserved("Reservation1"); _unitOfWorkManager.Current.ShouldNotBeNull(); _unitOfWorkManager.Current.Id.ShouldBe(uow1.Id); await uow1.CompleteAsync(); } _unitOfWorkManager.Current.ShouldBeNull();}

    通過對代碼的注釋和斷點調試的結果,我們知道了通過 Reserved 創建的工作單元它的?IsReserved?屬性是?true,所以我們調用?IUnitOfWorkManager.Current?訪問的時候,會忽略掉保留工作單元,所以得到的值就是?null。

    但是通過調用?BeginReserved(string name)?方法,我們就可以將指定的工作單元置為?當前工作單元,這是因為調用了該方法之后,會重新調用工作單元的?Initialize()?方法,在該方法內部,又會將?IsReserved?設置為?false?。

    public virtual void Initialize(UnitOfWorkOptions options){ IsReserved = false;}

    保留工作單元的用途主要是在某些特殊場合,在某些特定條件下不想暴露給?IUnitOfWorkManager.Current?時使用。

    2.5 工作單元攔截器

    如果我們每個地方都通過工作單元管理器來手動創建工作單元,那還是比較麻煩的。ABP vNext 通過攔截器,來為特定的類型(符合規則)自動創建工作單元。

    關于攔截器的注冊已經在文章最開始說明了,這里就不再贅述,我們直接來看攔截器的內部實現。其實在攔截器的內部,一樣是使用工作單元攔截器我來為我們創建工作單元的。只不過通過攔截器的方式,就能夠無感知/無侵入地為我們構造健壯的數據持久化機制。

    public override void Intercept(IAbpMethodInvocation invocation){ if (!UnitOfWorkHelper.IsUnitOfWorkMethod(invocation.Method, out var unitOfWorkAttribute)) { invocation.Proceed(); return; } using (var uow = _unitOfWorkManager.Begin(CreateOptions(invocation, unitOfWorkAttribute))) { invocation.Proceed(); uow.Complete(); }}

    關于在 ASP.NET Core MVC 的工作單元過濾器,在實現上與攔截器大同小異,后續講解 ASP.NET Core Mvc 時再著重說明。

    三、總結

    ABP vNext 框架通過統一工作單元為我們提供了健壯的數據庫訪問與持久化機制,使得開發人員在進行軟件開發時,只需要關注業務邏輯即可。不需要過多關注與數據庫等基礎設施的交互,這一切交由框架完成即可。

    這里多說一句,ABP vNext 本身就是面向 DDD 所設計的一套快速開發框架,包括值對象(ValueObject)這些領域驅動開發的特殊概念也被加入到框架實現當中。

    微服務作為 DDD 的一個典型實現,DDD 為微服務的劃分提供理論支持。這里為大家推薦《領域驅動設計:軟件核心復雜性應對之道》這本書,該書籍由領域驅動設計的提出者編寫。

    看了之后發現在大型系統當中(博主之前做 ERP 的,吃過這個虧)很多時候都是憑感覺來寫,沒有一個具體的理論來支持軟件開發。最近拜讀了上述書籍之后,發現領域驅動設計(DDD)就是一套完整的方法論(當然?不是銀彈)。大家在學習并理解了領域驅動設計之后,使用 ABP vNext 框架進行大型系統開發就會更加得心應手。

    四、后記

    關于本系列文章的更新,因為最近自己在做?物聯網(Rust 語言學習、數字電路設計)相關的開發工作,所以 5 月到 6 月這段時間都沒怎么去研究 ABP vNext。

    最近在學習領域驅動設計的過程中,發現 ABP vNext 就是為 DDD 而生的,所以趁熱打鐵想將后續的 ABP vNext 文章一并更新,預計在 7 月內會把剩余的文章補完(核心模塊)。

    原文地址:https://www.cnblogs.com/myzony/p/11112288.html


    .NET社區新聞,深度好文,歡迎訪問公眾號文章匯總?http://www.csharpkit.com?

    總結

    以上是生活随笔為你收集整理的[Abp vNext 源码分析] - 4. 工作单元的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。