設計基礎結構持續性層

提示

本內容節錄自《容器化 .NET 應用程式的 .NET 微服務架構》(.NET Microservices Architecture for Containerized .NET Applications) 電子書,可以在 .NET Docs 上取得,或免費下載可供離線閱讀的 PDF。

.NET Microservices Architecture for Containerized .NET Applications eBook cover thumbnail.

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

The Repository pattern (存放庫模式)\

存放庫模式是一種領域驅動設計模式,旨在將持續性考量排除在系統領域模型之外。 可在領域模型中定義一或多個持續性抽象概念 (介面),這些抽象概念會以應用程式其他位置定義的持續性特定配接器形式進行實作。

存放庫實作是封裝存取資料來源所需邏輯的類別。 它們會集中管理常見的資料存取功能,以提供更佳的可維護性,並將用來存取資料庫與用來存取領域模型的基礎結構或技術分離。 如果您使用類似 Entity Framework 的物件關聯式對應程式 (ORM),多虧有 LINQ 和強型別,必須實作的程式碼會經過簡化。 這可讓您專注於資料持續性邏輯,而不是資料存取連接。

儲存機制模式是使用資料來源之妥善記載的方法。 在 Patterns of Enterprise Application Architecture 一書中,Martin Fowler 對儲存機制有以下描述:

儲存機制會在領域模型層與資料對應之間執行中繼工作,其作用類似於記憶體內部的一組領域物件。 用戶端物件以宣告方式建立查詢,並將它們傳送至儲存機制以尋求解答。 就概念而言,儲存機制會封裝一組儲存在資料庫中的物件,以及可在其上執行的作業,來提供接近持續性層的方式。 儲存機制也支援清楚且一個方向的分離用途,以及工作領域與資料配置或對應之間的一致性。

每個彙總定義一個儲存機制

您應針對每個彙總或彙總根,建立一個儲存機制類別。 您也許可以利用 C# 泛型來減少您需要維護的實體類別總數 (如本章稍後所示)。 在以領域驅動設計 (DDD) 模式為基礎的微服務中,您唯一可用來更新資料庫的通道應該是存放庫。 這是因為它們具有一對一關聯性和彙總根,可控制彙總的不變式和交易一致性。 您還是可以透過其他通道來查詢資料庫 (例如您可以遵循 CQRS 方法來這樣做),因為查詢不會變更資料庫的狀態。 不過,交易區域 (也就是更新) 必須一律受到存放庫和彙總根的控制。

基本上,儲存機制可讓您將來自資料庫的資料,以領域實體形式填入記憶體。 一旦實體在記憶體中,就可以變更,然後透過交易保存回到資料庫。

如稍早所述,如果您使用 CQS/CQRS 架構模式,來自領域模型端的查詢 (由使用 Dapper 的簡單 SQL 陳述式來執行) 會執行初始查詢。 此方法比存放庫更有彈性,因為您可以查詢並聯結任何需要的資料表,而且這些查詢不會受到彙總規則的限制。 該資料會移至展示層或用戶端應用程式。

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

在這裡要特別再次強調的是,您應該只會針對每個彙總根定義一個存放庫,如圖 7-17 所示。 若要達到在彙總內所有物件之間維持交易一致性的彙總根目標,請絕對不要針對資料庫中的每個資料表建立儲存機制。

Diagram showing relationships of domain and other infrastructure.

圖 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 程式開發中實作的基本模式。

例如,當 Jimmy Bogard 對本指南提供直接意見反應時表示:

這可能是我最強烈的意見反應。 我其實不是存放庫的愛好者,主要是因為存放庫會隱藏基礎持續性機制的重要詳細資料。 這也是為什麼我使用 MediatR 命令的原因。 我可以利用持續性層的完整強大功能,並將所有領域行為推送至我的彙總根。 我通常不想要模擬存放庫 (我仍需要使用真實內容進行整合測試)。 移至 CQRS 表示我們實際上不再需要存放庫。

存放庫可能很有用,但它們對您的 DDD 設計並不如彙總模式和豐富領域模型同樣重要。 因此,是否使用儲存機制模式完全取決於您。

其他資源

存放庫模式

工作單位模式