2016 年 3 月

第 31 卷,第 3 期

本文章是由機器翻譯。

技術最前線 - CQRS 架構的查詢堆疊

Dino Esposito |2016 年 3 月

Dino Esposito目前命令與查詢責任隔離 (CQRS) 是其中一個最常用到架構的主題。基本上,CQRS 會是不超過常識和所有建議是,您的程式碼查詢和命令的系統需要透過不同且更特定的堆疊。您可能要有命令堆疊中的網域層級和組織在網域服務中的商務工作網域物件在組件的商務規則。保存層也可以自由地只挑選技術的來進行編碼,最佳的方法符合您的需求,是否儲存純文字的關聯式資料表、 NoSQL 或事件。

關於讀取堆疊,而是嗎? 好消息是,一旦您開始了解 CQRS 架構,通常您不想要有網域層只讀取資料的不同區段。一般的查詢,利用現成的資料表是您只需要。查詢堆疊可以執行唯讀作業對後端系統的狀態並不會變更。因此,您可能不需要任何形式的商務規則和展示層和儲存體之間的邏輯 intermediation — 或至少不超出 SQL 進階查詢運算子,例如 GROUP BY、 聯結和順序的基本功能。在現代查詢堆疊的實作中,支援 Microsoft.NET Framework 中的 LINQ 語言是十分有幫助。在本專欄的其餘部分,我將會經歷讀取堆疊可能實作其中儲存體設計成接近簡報所需的資料組織。

擁有現成的資料

在 CQRS 專案中,您通常計劃的查詢和命令堆疊個別的專案,您甚至可以擁有不同的小組的每個堆疊。讀取的堆疊基本上是包含兩個元件 ︰ 資料傳輸物件 (Dto) 來傳遞資料到展示層和資料庫內容來執行實體讀取並填入 Dto 的集合。

保留查詢堆疊,以了解,以及表示越好,您應該目標是具有儲存的資料與資料之間呈現接近的相符。尤其是這幾天,這可能不是如此 ︰ 它的多個常見資料案例在理想情況下會儲存在指定的格式,但必須以有效地使用明顯不同格式排列。舉例而言,將軟體系統在企業環境中的活頁簿會議室。[圖 1 提供圖形化檢視方塊的這種情況。

預約 CQRS 架構中的結構描述
[圖 1 預約 CQRS 架構中的結構描述

使用者提供的一些形式建立預約要求或更新現有的預約。因為是在命令資料存放區會記錄每個動作。捲動記錄的預約事件清單,其中一個可以輕鬆地追蹤修改幾次修改和刪除時進入系統,輸入每個登記時。這是絕對最有效的方式來儲存資訊的動態系統其中預存的項目狀態可能會隨著時間變更。

事件的形式儲存系統的狀態有許多優點,我年 8 月 2015年的摘要說明技術最前線 」 專欄 (msdn.com/magazine/mt185569),但它無法提供即時檢視系統的目前狀態。換句話說,您可以找出單一的預約,完整記錄,但是您不立即能夠傳回暫止的預約清單。使用者的系統,相反地,是通常也會有的預約清單。這會建立兩個不同的存放區保持同步處理的需求。每次新的事件記錄時,相關實體的狀態應該針對簡單的查詢 (同步或非同步) 更新。

在同步處理步驟時,您要確定有用的資料是從事件記錄中擷取和一般的格式,很容易取用從透過查詢堆疊 UI 項目。此時,所有查詢堆疊要進行的動作是目前檢視的特定資料模型的資料的圖形。

多個事件的角色

常見的物件時,「 為什麼我應該將儲存資料的事件表單中? 為什麼不只是將資料儲存在表單中會使用它? 」

答案是傳統、 「 它而定,「 和有趣的事是它不常取決於您的架構設計人員。商務需求而定。如果它是索引鍵客戶追蹤為商業項目 (也就是預約) 的歷程記錄,或參閱預約清單是某一天的聊天室的可用性會隨時間改變,最簡單的方式來解決問題時記錄事件,並在該建置任何必要的資料投影。

順便一提,這也是商務智慧 (BI) 服務和應用程式所使用的內部運作。透過使用事件,您甚至可以某些內部 bi 二維地面。

唯讀的 Entity Framework 內容

Entity Framework 是.NET 與 ASP.NET 應用程式內存取至少從儲存的資料,最常見的做法。Entity Framework 會透過 DbContext 類別的執行個體的資料庫呼叫。DbContext 類別提供基礎資料庫的讀取/寫入存取。如果您讓 DbContext 執行個體顯示從最上方的圖層,會公開您的架構,風險會接收來自查詢堆疊內的狀態更新。它也不是錯誤,本身。但是很嚴重您想要避免的架構規則的違規情形。若要避免資料庫的寫入權限,可能要換行 DbContext 執行個體中的容器和可處置的類別中所示 [圖 2

[圖 2 包裝在容器和可處置類別的 DbContext 執行個體

public class Database : IDisposable
{
  private readonly SomeDbContext _context = new SomeDbContext();
  public IQueryable<YourEntity> YourEntities
  {
    get
    {
      return _context.YourEntities;
    }
  }
  public void Dispose()
  {
    _context.Dispose();
  }
}

每當您想要使用的 DbContext 只來查詢資料,您可以使用資料庫。兩個層面使資料庫的類別不同於一般的 DbContext。首先和最重要的資料庫類別會封裝的 DbContext 執行個體的私用成員。第二,資料庫類別會公開所有或部分的 DbContext 集合做為 < T > IQueryable 集合,而不是 < T > DbSet 集合。這是無法加入、 刪除或只是將變更回儲存享受 LINQ 查詢發揮的技巧。

形成的資料檢視

在 CQRS 架構中,沒有任何標準站就有關應用程式層級的組織。它可以是不同的命令和查詢堆疊或唯一的圖層。大部分的情況下,決定會留給架構設計人員的願景。如果您管理以避免雙重的存放裝置,因此資料對簡報中,量值,則您幾乎不需要的任何複雜資料擷取的邏輯。接著,您可以將小查詢堆疊的實作。在應用程式層程式碼的查詢部分,可以直接資料存取程式碼也可以直接呼叫 Entity Framework DbContext 進入點。不過,真正使事情限制為查詢作業,您只使用資料庫類別而不是原生 DbContext。您收到來自資料庫類別的任何項目,即只可查詢透過 LINQ,不過。[圖 3 提供的範例。

圖 3 查詢從資料庫類別透過 LINQ

using (var db = new Database())
{
  var queryable = from i in db.Invoices
                              .Include("Customers")
    where i.InvoiceId == invoiceId
    select new InvoiceFoundViewModel
    {
      Id = i.InvoiceId,
      State = i.State.ToString(),
      Total = i.Total,
      Date = i.IssueDate,
      ExpiryDate = i.GetExpiryDate()
    };
    // More code here
}

如您所見,會指定實際的資料,您會取得從查詢投影在過去一分鐘,以滑鼠右鍵在應用程式層級。這表示您可以在基礎結構層級中的某處建立 IQueryable 物件和各層間移動。每個圖層都有機會進一步修改精簡查詢不會實際執行它的物件。如此一來,您不需要建立大量的圖層之間移動資料傳輸物件。

我們來看相當複雜的查詢。例如,假設其中一個應用程式中的使用案例需要擷取商務單位沒有已付費付款期限到期後的 30 天的所有發票。如果查詢表示穩定,以及合併商務需求,將不會進一步調整過程中,它就不會在一般 T-SQL 中撰寫這類的艱難查詢。查詢由三個主要部分組成 ︰ 取得所有發票選取這些特定的特定的商業單位,選取尚未尚未在 30 天後已付費。從實作觀點來看,其實沒有必要進行分割三個子查詢中的原始查詢,並進行大部分的記憶體中的工作。好,概念的觀點而言,分割三個部分中的查詢可它更容易了解,特別是針對網域新手適用。

LINQ 是神奇的工具,可讓您在概念上表示的查詢時,必須執行在單一步驟中,負責將它轉譯成適當的查詢語言的基礎 LINQ 提供者。以下是可行的方法,以表示 LINQ 查詢 ︰

var queryable = from i in db.Invoices
                            .Include("Customers")
  where i.BusinessUnitId == buId &&
     DateTime.Now – i.PaymentDueBy > 30
                select i;

運算式不會傳回資料,不過。運算式只會傳回尚未被執行的查詢。若要執行查詢並取得相關的資料,您必須叫用如 ToList 或 FirstOrDefault LINQ 執行程式。只有在該點查詢建置,將所有項目放在一起,並將資料傳回。

LINQ 和無所不在的語言

IQueryable 物件可以修改過程。您可以這樣做,藉由呼叫的位置,並以程式設計方式選取方法。如此一來,IQueryable 物件周遊的圖層的系統,因為最後一個查詢會形成透過篩選器的組合。而且,更重要的是,每個篩選條件會套用時只能使用其中所依據的邏輯。

另一個相關的一點有關 LINQ 和 IQueryable 物件查詢堆疊中的 CQRS 架構是可讀性和無所不在的語言。在網域導向設計 (DDD),無所不在的語言是建議您在開發和維護另一種明確的商業術語 (名詞和動詞命令) 的模式,更重要的是,反映在實際的程式碼中的這些詞彙。軟體系統中實作無所不在的語言,比方說,不會刪除順序但 「 取消 」 順序並不會提交訂單但是 「 取出。 」

新式軟體的最大的挑戰不是技術解決方案,但在深入了解商務需求與中尋找完美的相符項目之間的商務需求和程式碼。若要改善查詢的可讀性,您可以混合在一起 IQueryable 物件和 C# 擴充方法。以下是將許多更容易讀取上一個查詢重寫的方法 ︰

var queryable = from i in db.Invoices
                            .Include("Customers")
                            .ForBusinessUnit(buId)
                            .Unpaid(30)
                select i;

ForBusinessUnit 和 Unpaid 是擴充 IQueryable < 發票 > 類型的兩個使用者定義的擴充方法。這樣做的只是加入進行中查詢的定義中的 WHERE 子句 ︰

public static IQueryable<Invoice> ForBusinessUnit(
  this IQueryable<Invoice> query, int businessUnitId)
{
  var invoices =
    from i in query
    where i.BusinessUnit.OrganizationID == buId
    select i;
  return invoices;}

類似地,不支薪的方法將包含的另一個 WHERE 子句,以進一步限制所傳回的資料。最後一個查詢都相同,但它的運算式是很清楚,而且難以誤解。換句話說,透過使用擴充方法幾乎就定義域專屬語言,順便一提,這是其中一項 DDD 無所不在語言的目標。

總結

如果您比較單純 DDD 的 CQRS,您會看到在大部分情況下您可以降低複雜性網域分析和設計,方法是將焦點放在使用案例相關的模型,備份動作,變更系統的狀態。其他項目,也就是只讀取目前的狀態的動作可以透過簡單的程式碼基礎結構表示,而會比一般的資料庫查詢不更為複雜。

也請注意,查詢堆疊中的 CQRS 架構,有時甚至是全能 O/RM 可能會太麻煩了。在一天結束時,您只需要是大多數來自現成的關聯式資料表的查詢資料。例如消極式載入,群組聯結就不需要複雜的作業,您只需要已經存在,您需要。重要 O/RM 像 Entity Framework 6 可能甚至會為您的需求,太大,微 O/RMs 像 PetaPoco 或 NPoco 可能甚至可以執行此作業。有趣的是,這種趨勢也有一部分會反映在新的 Entity Framework 7 和新的 ASP.NET 5 堆疊的設計。


Dino Esposito是每個 「 Microsoft.NET: 架構的企業應用程式 」 (Microsoft Press,2014年) 和 [使用 ASP.NET 最新 Web 應用程式] (Microsoft Press,2016年)。.NET 和 Android 平台在 JetBrains,並在世界各地的產業活動的技術推廣人員 Esposito 分享軟體的願景 software2cents@wordpress.com 和 Twitter: @despos