隔離,通過使用不同的接口,從操作讀取數(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 方法有一些缺點:
注意: 對于的 CRUD 方法的局限性有了更深的了解請參見“CRUD,只有當(dāng)你能負(fù)擔(dān)得起”MSDN 上。
命令和查詢職責(zé)分離(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)移副本,以最大限度地提高可用性。
讀的分離和寫入存儲還允許每個到會適當(dāng)縮放以匹配負(fù)載。例如,讀取存儲通常會遇到一個更高的負(fù)載寫入存儲。
當(dāng)查詢/讀取模型中包含的非規(guī)范化的信息(見物化視圖模式),性能正在讀取數(shù)據(jù)的每一個視圖時在應(yīng)用程序中或在查詢系統(tǒng)中的數(shù)據(jù)時最大化。
有關(guān)CQRS模式及其實現(xiàn)的詳細信息,請參閱以下資源:
在決定如何實現(xiàn)這個模式時,請考慮以下幾點:
對于最終一致性的說明,請參閱數(shù)據(jù)一致性底漆。
這種模式非常適合于:
這種模式可能不適合于下列情況:
CQRS 模式常用于與事件獲取圖案一起使用。 CQRS 為基礎(chǔ)的系統(tǒng)使用分離的讀取和寫入的數(shù)據(jù)模型,每個針對有關(guān)任務(wù)和通常位于物理上分離的存儲區(qū)。當(dāng)與采購活動時,事件的存儲是寫模式,這是信息的權(quán)威來源。一個 CQRS 為基礎(chǔ)的系統(tǒng)的讀取模型提供數(shù)據(jù)的物化視圖,通常是高度非規(guī)范化的意見。這些視圖量身定做的接口和應(yīng)用程序,這有助于最大程度地顯示和查詢性能的顯示要求。
使用事件作為寫入存儲區(qū),而不是實際的數(shù)據(jù)的流,在一個時間點,避免了在單個聚合更新沖突并最大限度地提高性能和可擴展性。該事件可用于異步生成用于填充讀取存儲器中的數(shù)據(jù)的實體化視圖。
由于事件存儲是信息的權(quán)威來源,就可以刪除物化視圖和回放所有過去的事件來創(chuàng)建當(dāng)前狀態(tài)的一個新表示當(dāng)系統(tǒng)升級時,或者當(dāng)讀取模式必須改變。物化視圖是有效的數(shù)據(jù)的耐用只讀緩存。
當(dāng)使用 CQRS 結(jié)合事件獲取模式,考慮以下幾點:
注意: 欲了解更多信息,請參閱活動采購模式和物化視圖模式,以及模式與實踐指導(dǎo) 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ù)及庫存管理的方式。
更多建議: