共用方式為


設計基礎結構持續性層

小提示

此內容是適用於容器化 .NET 應用程式的電子書.NET 微服務架構摘錄,可在 .NET Docs 或免費下載的 PDF 中取得,可脫機讀取。

.NET 微服務架構的容器化 .NET 應用程式電子書封面縮圖。

數據持續性元件可讓您存取裝載在微服務界限內的數據(也就是微服務的資料庫)。 它們包含元件的實際實作,例如存放庫和 工作單位 類別,例如自定義 Entity Framework (EF) DbContext 物件。 EF DbContext 會同時實作存放庫和工作單位模式。

存放庫模式

存放庫模式是 Domain-Driven 設計模式,旨在將持續性考慮保留在系統領域模型之外。 一或多個持續性抽象概念 - 介面 - 定義於領域模型中,而這些抽象概念的實作形式為應用程式中其他地方定義的持續性特定配接器。

存放庫實作是類別,封裝存取數據源所需的邏輯。 它們會集中一般數據存取功能,以提供更佳的可維護性,並分離用來從網域模型存取資料庫的基礎結構或技術。 如果您使用像 Entity Framework 這樣的 Object-Relational Mapper(ORM),則需要實作的程式碼變得更簡單,這要感謝 LINQ 和強型別。 這可讓您專注於數據持續性邏輯,而不是數據存取管道。

存放庫模式是一種妥善記載的數據源使用方式。 在企業 應用程式架構模式一書中,Martin Fowler 描述存放庫,如下所示:

存放庫會執行領域模型層與數據對應之間的中繼工作,其運作方式與記憶體中的一組領域對象類似。 客戶端物件會以宣告方式建置查詢,並將其傳送至存放庫以取得答案。 在概念上,存放庫封裝了一組儲存在資料庫中的物件及其可執行的操作,並提供了一種更接近持久層的方法。 存放庫也支援區分工作域與數據配置或對應之間的相依性,以及明確且以單一方向分隔的目的。

為每個匯總定義一個存放庫

針對每個聚合或聚合根,您應該建立一個儲存庫類別。 您可以利用 C# 泛型來減少您需要維護的具體類別總數(如本章稍後所示)。 在以 Domain-Driven Design (DDD) 模式為基礎的微服務中,您唯一應該用來更新資料庫的通道應該是存放庫。 這是因為它們與聚合根具有一對一的關係,控制聚合的不變和交易的一致性。 您可以透過其他通道查詢資料庫(就像您可以遵循 CQRS 方法一樣),因為查詢不會變更資料庫的狀態。 不過,交易區域(也就是更新)必須一律由存放庫和匯總根目錄控制。

基本上,存放庫可讓您以網域實體的形式填入來自資料庫的記憶體中的數據。 完成實體載入到記憶體後,可以變更其內容,並透過交易將其持久化回資料庫。

如先前所述,如果您使用 CQS/CQRS 架構模式,初始查詢會由在網域模型外執行的附屬查詢完成,這些附屬查詢使用 Dapper 透過簡單的 SQL 語句執行。 這種方法比存放庫更有彈性,因為您可以查詢和聯結任何您需要的數據表,而且這些查詢不受匯總規則的限制。 該數據會移至表示層或用戶端應用程式。

如果使用者進行變更,要更新的數據會來自用戶端應用程式或表示層到應用層(例如 Web API 服務)。 當您在命令處理程式中收到命令時,您可以使用存放庫從資料庫取得想要更新的數據。 您會使用以命令傳遞的數據在記憶體中更新它,然後透過交易在資料庫中新增或更新數據(網域實體)。

請務必再次強調,您應該只為每個匯總根目錄定義一個存放庫,如圖 7-17 所示。 若要達成匯總根目錄的目標,以維護匯總內所有對象之間的交易一致性,您絕對不應該為資料庫中的每個數據表建立存放庫。

顯示網域和其他基礎結構關聯性的圖表。

圖 7-17。 存放庫、匯總和資料庫數據表之間的關聯性

上圖顯示網域和基礎結構層之間的關聯性:購買者匯總相依於 IBuyerRepository 介面,訂單匯總相依於 IOrderRepository 介面,這些介面是在基礎結構層中由對應的存放庫實作,這些存放庫相依於在該層中實作的 UnitOfWork,用來存取資料層中的數據表。

強制設置每個資料庫一個聚合根

正確地執行資料庫設計可能很有價值,以便確保僅有聚合根可以擁有資料庫。 您可以建立泛型或基底存放庫類型,以限制其使用的實體類型,以確保它們具有 IAggregateRoot 標記介面。

因此,在基礎結構層實作的每個存放庫類別都會實作自己的合約或介面,如下列程式代碼所示:

namespace Microsoft.eShopOnContainers.Services.Ordering.Infrastructure.Repositories
{
    public class OrderRepository : IOrderRepository
    {
      // ...
    }
}

每個特定存放庫介面都會實作泛型 IRepository 介面:

public interface IOrderRepository : IRepository<Order>
{
    Order Add(Order order);
    // ...
}

不過,讓程式碼遵循慣例,使每個倉儲都與單一聚合相關,可以透過實作一個泛型倉儲類型來達成。 如此一來,您就明確使用儲存庫以特定聚合為目標。 實作泛型 IRepository 基底介面即可輕鬆完成,如下列程式代碼所示:

public interface IRepository<T> where T : IAggregateRoot
{
    //....
}

存放庫模式可讓您更輕鬆地測試應用程式邏輯

存放庫模式可讓您輕鬆地使用單元測試來測試應用程式。 請記住,單元測試只會測試您的程序代碼,而不是基礎結構,因此存放庫抽象概念可讓您更輕鬆地達成該目標。

如前一節所述,建議您在領域模型層中定義並放置存放庫介面,讓應用層,例如Web API微服務,不直接相依於您已實作實際存放庫類別的基礎結構層。 透過在 Web API 控制器中使用依賴注入,您可以建立模擬的存放庫,以返回模擬數據而不是從資料庫中返回真實數據。 這個分離的方法可讓您建立和執行單元測試,以將應用程式的邏輯放在焦點,而不需要連線到資料庫。

對資料庫的連線可能會失敗,更重要的是,對資料庫進行數百項測試有兩個負面影響。 首先,由於大量的測試,可能需要很長的時間。 其次,資料庫記錄可能會變更並影響測試的結果,特別是如果您的測試以平行方式執行,因此它們可能不一致。 單元測試通常可以平行執行;整合測試可能不支援平行執行,視其實作而定。 針對資料庫進行測試不是單元測試,而是整合測試。 您應該會快速執行許多單元測試,但對資料庫的整合測試較少。

在單元測試中關注分離的考量下,您的邏輯會在記憶體中的領域實體上執行。 它會假設存放庫類別已傳遞這些類別。 一旦邏輯修改網域實體,它會假設存放庫類別會正確儲存它們。 這裏的重點是針對您的領域模型及其領域邏輯建立單元測試。 匯總根是 DDD 中的主要一致性界限。

eShopOnContainers 中實作的存放庫依賴 EF Core 的 DbContext 實作,並使用其變更追蹤器來實作存放庫和工作單位模式,因此它們不會複製此功能。

存放庫模式與舊版數據存取類別 (DAL 類別) 模式之間的差異

典型的 DAL 物件會直接對記憶體執行資料存取和持續性作業,通常是在單一數據表和數據列的層級。 使用一組 DAL 類別實作的簡單 CRUD 作業通常不支援交易(雖然情況不一定如此)。 大部分 DAL 類別方法僅使用少量的抽象,這樣會導致呼叫 DAL 物件的應用程式或商業邏輯層(BLL)類別之間緊密耦合。

使用存放庫時,持續性的實作詳細數據會與領域模型分開封裝。 使用抽象可讓您透過裝飾器或 Proxy 等模式輕鬆擴充行為。 例如,跨領域考慮,例如 快取、記錄和錯誤處理,都可以使用這些模式來套用,而不是在數據存取程序代碼本身硬式編碼。 也很容易支援多個存放庫配接器,這些配接器可用於不同的環境中,從本機開發到共用準備環境再到生產環境。

實作工作單位

工作單位是指涉及多個插入、更新或刪除作業的單一交易。 簡單來說,這表示針對特定的用戶動作,例如網站上的註冊,所有插入、更新和刪除作業都會在單一交易中處理。 這比以聊天方式處理多個資料庫作業更有效率。

應用層的指令下達後,這些多個持續性作業會在單一動作中執行。 將記憶體內部變更套用至實際資料庫記憶體的決定通常是以工作單位模式為基礎。 在 EF 中,工作單位模式是由 DbContext 實作,並在呼叫 SaveChanges時執行。

在許多情況下,此模式或對記憶體套用作業的方式可能會增加應用程式效能,並減少不一致的可能性。 它也會減少資料庫數據表中的交易封鎖,因為所有預定的作業都會認可為一個交易的一部分。 相較於對資料庫執行許多隔離作業,這更有效率。 因此,選取的 ORM 可以透過將多個更新動作合併在單一交易中,而非執行多次小型且獨立的交易,來優化資料庫的執行。

您可以使用或不使用存放庫模式來實作工作單位模式。

存放庫不應強制

自定義存放庫適用於稍早所述的原因,而這是 eShopOnContainers 中訂購微服務的方法。 不過,在 DDD 設計或甚至是一般 .NET 開發中,這並非必要的模式。

例如,吉米·柏格德在為本指南提供直接意見反應時說:

這可能會是我最大的回饋。 我真的不是存放庫的粉絲,主要是因為它們隱藏基礎持續性機制的重要詳細數據。 這就是為什麼我選擇使用 MediatR 來處理命令。 我可以使用持續性層的完整功能,並將所有定義域行為推送至我的匯總根。 我通常不想模擬存放庫 , 我仍然需要使用真實專案進行整合測試。 採用 CQRS 意味著我們不再需要存儲庫。

儲存庫可能很有用,但它們對於您的 DDD 設計而言並不如匯總模式和豐富領域模型那樣重要。 因此,您可以根據需要選擇使用或不使用存放庫模式。

其他資源

存放庫模式

工作單位模式