(六)——命令和查詢職責分離(CQRS)模式

2018-02-24 15:44 更新

云計算設(shè)計模式(六)——命令和查詢職責分離(CQRS)模式

隔離,通過使用不同的接口,從操作讀取數(shù)據(jù)更新數(shù)據(jù)的操作。這種模式可以最大限度地提高性能,可擴展性和安全性;支持系統(tǒng)在通過較高的靈活性,時間的演變;防止更新命令,從造成合并在域級別上的沖突。

背景和問題

在傳統(tǒng)的數(shù)據(jù)管理系統(tǒng)中,這兩個命令(更新數(shù)據(jù))和查詢(請求數(shù)據(jù)),針對在一個單一的數(shù)據(jù)存儲庫中的相同的一組實體的執(zhí)行。這些實體可以是在關(guān)系數(shù)據(jù)庫中的一個或多個表,如 SQL Server 的行的子集。

典型地,在這些系統(tǒng)中,所有的創(chuàng)建,讀取,更新和刪除(CRUD)操作被施加到該實體的相同的表示。例如,一個數(shù)據(jù)傳輸對象(DTO)的代表顧客從數(shù)據(jù)存儲中檢索由數(shù)據(jù)訪問層(DAL)并顯示在屏幕上。用戶更新 DTO 的某些領(lǐng)域(也許是通過數(shù)據(jù)綁定)和 DTO,然后保存回數(shù)據(jù)存儲在 DAL。相同的 DTO 同時用于讀取和寫入操作,如圖1所示。

圖1 - 一個傳統(tǒng)的 CRUD 架構(gòu)

傳統(tǒng)的 CRUD 設(shè)計工作良好時,只有施加到數(shù)據(jù)操作有限的業(yè)務(wù)邏輯。由開發(fā)工具提供可以非??焖俚貏?chuàng)建數(shù)據(jù)訪問代碼的支架機構(gòu),根據(jù)需要,可再進行定制。

然而,傳統(tǒng)的 CRUD 方法有一些缺點:

  • 它往往意味著存在所述讀取和寫入的數(shù)據(jù),如額外的列或?qū)傩?,即使它們不是必需的作為操作的一部分,必須正確地更新的表示之間的不匹配。
  • 它遇到風險的數(shù)據(jù)爭用一個協(xié)作領(lǐng)域(在多個參與者??并行運行在相同的數(shù)據(jù)集)時,記錄被鎖定在數(shù)據(jù)存儲,或者更新沖突所造成的并發(fā)更新時,樂觀鎖使用。這些風險增加的復(fù)雜性和系統(tǒng)的吞吐量增加。此外,傳統(tǒng)的方法也可以對性能有負面影響,由于加載的數(shù)據(jù)存儲和數(shù)據(jù)訪問層上,并在檢索信息需要查詢的復(fù)雜度。
  • 它可以使安全管理和權(quán)限比較繁瑣,因為每一個實體是受讀取和寫入操作,這可能會在不經(jīng)意間暴露的數(shù)據(jù)在錯誤的情況下。

注意: 對于的 CRUD 方法的局限性有了更深的了解請參見“CRUD,只有當你能負擔得起”MSDN 上。

解決方案

命令和查詢職責分離(CQRS)是偏析,通過使用獨立的接口讀取操作的更新數(shù)據(jù)(命令)的數(shù)據(jù)(查詢)的操作模式。這意味著,用于查詢和更新的數(shù)據(jù)模型是不同的。該模型可隨后被分離,如在圖 2 中,雖然這不是絕對的要求。

圖2 - 一個基本的 CQRS 架構(gòu)

相比于數(shù)據(jù)(從該開發(fā)商建立自己的概念模式)的單個模型中固有的 CRUD 為基礎(chǔ)的系統(tǒng)中,使用單獨的查詢和更新模型中 CQRS 為基礎(chǔ)的系統(tǒng)中的數(shù)據(jù)顯著地簡化設(shè)計和實施。然而,一個缺點是,不像 CRUD 的設(shè)計,CQRS 代碼不能自動用支架的機制產(chǎn)生。

查詢模型讀取數(shù)據(jù)和寫入數(shù)據(jù)可以訪問相同的實體店,也許是通過使用 SQL 視圖的更新模型,或產(chǎn)生對飛預(yù)測。但是,它是常見的數(shù)據(jù)分成不同的物理存儲來提高性能,可擴展性和安全性;如圖3。

圖3 - 一個 CQRS 架構(gòu),具有獨立讀寫店

所讀取的存儲可以是只讀副本寫入存儲區(qū),或讀取和寫入存儲可以具有不同的結(jié)構(gòu)完全。使用 read 店的多個只讀副本可以大大提高查詢性能和應(yīng)用程序的UI響應(yīng)速度,尤其是在分布式場景下的只讀副本靠近應(yīng)用程序?qū)嵗?。一些?shù)據(jù)庫系統(tǒng),如 SQL Server,提供額外的功能,如故障轉(zhuǎn)移副本,以最大限度地提高可用性。

讀的分離和寫入存儲還允許每個到會適當縮放以匹配負載。例如,讀取存儲通常會遇到一個更高的負載寫入存儲。

當查詢/讀取模型中包含的非規(guī)范化的信息(見物化視圖模式),性能正在讀取數(shù)據(jù)的每一個視圖時在應(yīng)用程序中或在查詢系統(tǒng)中的數(shù)據(jù)時最大化。

有關(guān)CQRS模式及其實現(xiàn)的詳細信息,請參閱以下資源:

  • 該模式與實踐指導 CQRS 之旅 MSDN 上。尤其是你應(yīng)該閱讀的章節(jié)介紹了命令查詢職責分離方式進行全面的探索模式,當它是有用的,這一章尾聲:經(jīng)驗教訓,了解一些,可以使用這種模式時出現(xiàn)的問題。
  • 該職位 CQRS 由馬丁·福勒,這也解釋了該模式的基本知識,并鏈接到其他一些有用的資源。
  • 代碼更好的網(wǎng)站,它探討的 CQRS 模式的許多方面對 Greg Young 的帖子。

問題和注意事項

在決定如何實現(xiàn)這個模式時,請考慮以下幾點:

  • 分割數(shù)據(jù)存儲到單獨的物理存儲用于讀操作和寫操作可以提高系統(tǒng)的性能和安全性,但它可以在彈性和最終一致性方面增加了相當大的復(fù)雜性。所讀取的模型存儲必須被更新以反映變化的寫入模型存儲,并且它可以是難以檢測用戶何時發(fā)出基于讀取過時數(shù)據(jù)意味著該操作不能完成的請求。

注意

對于最終一致性的說明,請參閱數(shù)據(jù)一致性底漆。

  • 考慮 CQRS 應(yīng)用到你的系統(tǒng)中的限制部分地方將是最有價值的,并從經(jīng)驗中學習。
  • 的典型方法擁抱最終一致性是使用事件采購與 CQRS 結(jié)合使寫模式是由執(zhí)行命令的驅(qū)動事件的追加只流。這些事件被用來更新充當讀取模型化視圖。欲了解更多信息,請參閱事件獲取和 CQRS。

當使用這個模式

這種模式非常適合于:

  • 其中并行地對相同的數(shù)據(jù)進行多項操作協(xié)同域。 CQRS 允許你有足夠的粒度定義的命令,以盡量減少在域級別(或者不出現(xiàn)可以通過在命令合并的沖突)的合并沖突,更新這似乎是同一類型的數(shù)據(jù)時也是如此。
  • 使用與基于任務(wù)的用戶界面(其中用戶通過一個復(fù)雜的過程引導作為一系列步驟),具有復(fù)雜的領(lǐng)域模型,以及用于團隊已經(jīng)熟悉領(lǐng)域驅(qū)動設(shè)計(DDD)技術(shù)。在寫入模式有一個完整的命令處理棧與業(yè)務(wù)邏輯,輸入驗證和業(yè)務(wù)驗證,以確保一切總是為每個聚集體(被視為一個單元進行數(shù)據(jù)變更的目的相關(guān)聯(lián)的對象的每個集群相一致)中的寫入模式。讀出的模型沒有業(yè)務(wù)邏輯或驗證的堆棧,只是返回一個 DTO 在一個視圖模型的使用。讀出的模型與模型寫入最終一致。
  • 方案,其中數(shù)據(jù)的讀出性能,必須分別從數(shù)據(jù)的性能進行微調(diào)寫入,尤其是當讀/寫比是非常高的,并且當水平擴展是必要的。例如,在許多系統(tǒng)中的讀取操作的數(shù)目是幾個數(shù)量級更大的寫入操作的數(shù)目。為了適應(yīng)這種情況,考慮向外擴展的讀取模式,但只在一個或幾個實例中運行的寫模式。少數(shù)寫入模型實例也有助于減少合并沖突的發(fā)生。
  • 場景的開發(fā)者之一的團隊可以專注于復(fù)雜的領(lǐng)域模型,它是寫模型的一部分??,而另一個經(jīng)驗不足的團隊可以專注于讀模型和用戶界面。
  • 場景中,預(yù)計隨著時間的推移,系統(tǒng),并且可以包含多個版本的模型,或者業(yè)務(wù)規(guī)則經(jīng)常改變。
  • 與其他系統(tǒng),特別是與事件采購,其中一個子系統(tǒng)的瞬時故障不會影響到其它的可用性的組合一體化。

這種模式可能不適合于下列情況:

  • 凡域或業(yè)務(wù)規(guī)則很簡單。
  • 凡一個簡單的 CRUD 風格的用戶界面和相關(guān)的數(shù)據(jù)訪問操作就足夠了。
  • 對于在整個系統(tǒng)中的實現(xiàn)。有一個整體的數(shù)據(jù)管理方案,其中 CQRS 可以是有用的特定組件,但是它可以增加它實際上并不需要相當大的和往往是不必要的復(fù)雜性。

事件獲取和 CQRS

CQRS 模式常用于與事件獲取圖案一起使用。 CQRS 為基礎(chǔ)的系統(tǒng)使用分離的讀取和寫入的數(shù)據(jù)模型,每個針對有關(guān)任務(wù)和通常位于物理上分離的存儲區(qū)。當與采購活動時,事件的存儲是寫模式,這是信息的權(quán)威來源。一個 CQRS 為基礎(chǔ)的系統(tǒng)的讀取模型提供數(shù)據(jù)的物化視圖,通常是高度非規(guī)范化的意見。這些視圖量身定做的接口和應(yīng)用程序,這有助于最大程度地顯示和查詢性能的顯示要求。

使用事件作為寫入存儲區(qū),而不是實際的數(shù)據(jù)的流,在一個時間點,避免了在單個聚合更新沖突并最大限度地提高性能和可擴展性。該事件可用于異步生成用于填充讀取存儲器中的數(shù)據(jù)的實體化視圖。

由于事件存儲是信息的權(quán)威來源,就可以刪除物化視圖和回放所有過去的事件來創(chuàng)建當前狀態(tài)的一個新表示當系統(tǒng)升級時,或者當讀取模式必須改變。物化視圖是有效的數(shù)據(jù)的耐用只讀緩存。

當使用 CQRS 結(jié)合事件獲取模式,考慮以下幾點:

  • 與任何系統(tǒng),其中寫入和讀出存儲是分開的,在此基礎(chǔ)上圖案系統(tǒng)唯一最終一致。將有被生成的事件和數(shù)據(jù)存儲器保存由這些事件被更新啟動操作的結(jié)果之間有一些延遲。
  • 該模式引入由于代碼必須創(chuàng)建啟動和處理事件,并組裝或者更新查詢或讀取模型所需的適當?shù)囊庖娀蛭矬w額外的復(fù)雜性。在采購活動一起使用的 CQRS 模式固有的復(fù)雜性時,可以做一個成功的實現(xiàn)更加困難,需要重新學習的一些概念和不同的方法來設(shè)計系統(tǒng)。然而,事件采購可以更容易地對域進行建模,并且可以更容易地重建的觀點或創(chuàng)建新的,因為變化的數(shù)據(jù)的意圖將被保留。
  • 生成物化視圖中讀取模型或數(shù)據(jù)通過重放和處理為特定的實體或?qū)嶓w的集合的事件突起的使用可能需要相當多的處理時間和資源的使用,尤其是如果它需要求和或值的數(shù)據(jù)在長時間內(nèi)的,因為所有的相關(guān)聯(lián)的事件可能需要被審查。這可以通過實現(xiàn)數(shù)據(jù)的快照在預(yù)定的時間間隔,如已經(jīng)發(fā)生的特定操作的次數(shù),或一個實體的當前狀態(tài)的總計數(shù)被部分地解決。

注意: 欲了解更多信息,請參閱活動采購模式和物化視圖模式,以及模式與實踐指導 CQRS 之旅 MSDN 上。尤其是你應(yīng)該閱讀的章節(jié)介紹采購活動進行全面的探索模式,以及它如何與 CQRS 有用的,而章 CQRS 和 ES 深潛了解更多,包括如何聚集分區(qū)可以在微軟的 Azure CQRS 使用。

例子

下面的代碼顯示了一個 CQRS 實現(xiàn),它使用不同的定義讀取和寫入模型為例某些提取物。該模型的接口沒有規(guī)定的基礎(chǔ)數(shù)據(jù)存儲的任何功能,并且可以發(fā)展和進行微調(diào)獨立,因為這些接口是分開的。

下面的代碼演示了讀取的模型定義。

// Query interface  
namespace ReadModel  
{  
  public interface ProductsDao  
  {  
    ProductDisplay FindById(int productId);  
    IEnumerable<ProductDisplay> FindByName(string name);  
    IEnumerable<ProductInventory> FindOutOfStockProducts();  
    IEnumerable<ProductDisplay> FindRelatedProducts(int productId);  
  }  
?
  public class ProductDisplay  
  {  
    public int ID { get; set; }  
    public string Name { get; set; }  
    public string Description { get; set; }  
    public decimal UnitPrice { get; set; }  
    public bool IsOutOfStock { get; set; }  
    public double UserRating { get; set; }  
  }  
?
  public class ProductInventory  
  {  
    public int ID { get; set; }  
    public string Name { get; set; }  
    public int CurrentStock { get; set; }  
  }  
}

該系統(tǒng)允許用戶率的產(chǎn)品。應(yīng)用程序代碼通過使用在下面的代碼中所示的 RateProduct 命令執(zhí)行此操作。

public interface Icommand  
{  
  Guid Id { get; }  
}  
?
public class RateProduct : Icommand  
{  
  public RateProduct()  
  {  
    this.Id = Guid.NewGuid();  
  }  
  public Guid Id { get; set; }  
  public int ProductId { get; set; }  
  public int rating { get; set; }  
  public int UserId {get; set; }  
}

本系統(tǒng)采用 ProductsCommandHandler 類來處理由應(yīng)用程序發(fā)出的命令??蛻舳送ǔMㄟ^消息傳送系統(tǒng)發(fā)送命令到域,如一個隊列。命令處理程序接受這些命令,并調(diào)用域接口的方法。每個命令的粒度被設(shè)計成減輕沖突請求的機會。下面的代碼顯示了 ProductsCommandHandler 類的輪廓。

public class ProductsCommandHandler :   
    ICommandHandler<AddNewProduct>,  
    ICommandHandler<RateProduct>,  
    ICommandHandler<AddToInventory>,  
    ICommandHandler<ConfirmItemShipped>,  
    ICommandHandler<UpdateStockFromInventoryRecount>      
{  
  private readonly IRepository<Product> repository;  
?
  public ProductsCommandHandler (IRepository<Product> repository)  
  {  
    this.repository = repository;  
  }  
?
  void Handle (AddNewProduct command)  
  {  
    ...  
  }  
?
  void Handle (RateProduct command)  
  {  
    var product = repository.Find(command.ProductId);  
    if (product != null)  
    {  
      product.RateProuct(command.UserId, command.rating);  
      repository.Save(product);  
    }  
  }  
?
  void Handle (AddToInventory command)  
  {  
    ...  
  }  
?
  void Handle (ConfirmItemsShipped command)  
  {  
    ...  
  }  
?
  void Handle (UpdateStockFromInventoryRecount command)  
  {  
    ...  
  }  
}

下面的代碼顯示了寫模式 ProductsDoman 接口。

public interface ProductsDomain  
{  
  void AddNewProduct(int id, string name, string description, decimal price);  
  void RateProduct(int userId int rating);  
  void AddToInventory(int productId, int quantity);  
  void ConfirmItemsShipped(int productId, int quantity);  
  void UpdateStockFromInventoryRecount(int productId, int updatedQuantity);  
}

還要注意如何 ProductsDomain 接口包含在域中的意義的方法。通常情況下,在一個 CRUD 環(huán)境中,這些方法將有通用名稱,如保存或更新,并有一個 DTO 作為唯一的參數(shù)。該 CQRS 方法可以更好地定制,以滿足該組織開展業(yè)務(wù)及庫存管理的方式。

以上內(nèi)容是否對您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號
微信公眾號

編程獅公眾號