隔離,通過使用不同的接口,從操作讀取數(shù)據(jù)更新數(shù)據(jù)的操作。這種模式可以最大限度地提高性能,可擴(kuò)展性和安全性;支持系統(tǒng)在通過較高的靈活性,時(shí)間的演變;防止更新命令,從造成合并在域級(jí)別上的沖突。
在傳統(tǒng)的數(shù)據(jù)管理系統(tǒng)中,這兩個(gè)命令(更新數(shù)據(jù))和查詢(請(qǐng)求數(shù)據(jù)),針對(duì)在一個(gè)單一的數(shù)據(jù)存儲(chǔ)庫(kù)中的相同的一組實(shí)體的執(zhí)行。這些實(shí)體可以是在關(guān)系數(shù)據(jù)庫(kù)中的一個(gè)或多個(gè)表,如 SQL Server 的行的子集。
典型地,在這些系統(tǒng)中,所有的創(chuàng)建,讀取,更新和刪除(CRUD)操作被施加到該實(shí)體的相同的表示。例如,一個(gè)數(shù)據(jù)傳輸對(duì)象(DTO)的代表顧客從數(shù)據(jù)存儲(chǔ)中檢索由數(shù)據(jù)訪問層(DAL)并顯示在屏幕上。用戶更新 DTO 的某些領(lǐng)域(也許是通過數(shù)據(jù)綁定)和 DTO,然后保存回?cái)?shù)據(jù)存儲(chǔ)在 DAL。相同的 DTO 同時(shí)用于讀取和寫入操作,如圖1所示。
圖1 - 一個(gè)傳統(tǒng)的 CRUD 架構(gòu)
傳統(tǒng)的 CRUD 設(shè)計(jì)工作良好時(shí),只有施加到數(shù)據(jù)操作有限的業(yè)務(wù)邏輯。由開發(fā)工具提供可以非??焖俚貏?chuàng)建數(shù)據(jù)訪問代碼的支架機(jī)構(gòu),根據(jù)需要,可再進(jìn)行定制。
然而,傳統(tǒng)的 CRUD 方法有一些缺點(diǎn):
注意: 對(duì)于的 CRUD 方法的局限性有了更深的了解請(qǐng)參見“CRUD,只有當(dāng)你能負(fù)擔(dān)得起”MSDN 上。
命令和查詢職責(zé)分離(CQRS)是偏析,通過使用獨(dú)立的接口讀取操作的更新數(shù)據(jù)(命令)的數(shù)據(jù)(查詢)的操作模式。這意味著,用于查詢和更新的數(shù)據(jù)模型是不同的。該模型可隨后被分離,如在圖 2 中,雖然這不是絕對(duì)的要求。
圖2 - 一個(gè)基本的 CQRS 架構(gòu)
相比于數(shù)據(jù)(從該開發(fā)商建立自己的概念模式)的單個(gè)模型中固有的 CRUD 為基礎(chǔ)的系統(tǒng)中,使用單獨(dú)的查詢和更新模型中 CQRS 為基礎(chǔ)的系統(tǒng)中的數(shù)據(jù)顯著地簡(jiǎn)化設(shè)計(jì)和實(shí)施。然而,一個(gè)缺點(diǎn)是,不像 CRUD 的設(shè)計(jì),CQRS 代碼不能自動(dòng)用支架的機(jī)制產(chǎn)生。
查詢模型讀取數(shù)據(jù)和寫入數(shù)據(jù)可以訪問相同的實(shí)體店,也許是通過使用 SQL 視圖的更新模型,或產(chǎn)生對(duì)飛預(yù)測(cè)。但是,它是常見的數(shù)據(jù)分成不同的物理存儲(chǔ)來(lái)提高性能,可擴(kuò)展性和安全性;如圖3。
圖3 - 一個(gè) CQRS 架構(gòu),具有獨(dú)立讀寫店
所讀取的存儲(chǔ)可以是只讀副本寫入存儲(chǔ)區(qū),或讀取和寫入存儲(chǔ)可以具有不同的結(jié)構(gòu)完全。使用 read 店的多個(gè)只讀副本可以大大提高查詢性能和應(yīng)用程序的UI響應(yīng)速度,尤其是在分布式場(chǎng)景下的只讀副本靠近應(yīng)用程序?qū)嵗?。一些?shù)據(jù)庫(kù)系統(tǒng),如 SQL Server,提供額外的功能,如故障轉(zhuǎn)移副本,以最大限度地提高可用性。
讀的分離和寫入存儲(chǔ)還允許每個(gè)到會(huì)適當(dāng)縮放以匹配負(fù)載。例如,讀取存儲(chǔ)通常會(huì)遇到一個(gè)更高的負(fù)載寫入存儲(chǔ)。
當(dāng)查詢/讀取模型中包含的非規(guī)范化的信息(見物化視圖模式),性能正在讀取數(shù)據(jù)的每一個(gè)視圖時(shí)在應(yīng)用程序中或在查詢系統(tǒng)中的數(shù)據(jù)時(shí)最大化。
有關(guān)CQRS模式及其實(shí)現(xiàn)的詳細(xì)信息,請(qǐng)參閱以下資源:
在決定如何實(shí)現(xiàn)這個(gè)模式時(shí),請(qǐng)考慮以下幾點(diǎn):
對(duì)于最終一致性的說明,請(qǐng)參閱數(shù)據(jù)一致性底漆。
這種模式非常適合于:
這種模式可能不適合于下列情況:
CQRS 模式常用于與事件獲取圖案一起使用。 CQRS 為基礎(chǔ)的系統(tǒng)使用分離的讀取和寫入的數(shù)據(jù)模型,每個(gè)針對(duì)有關(guān)任務(wù)和通常位于物理上分離的存儲(chǔ)區(qū)。當(dāng)與采購(gòu)活動(dòng)時(shí),事件的存儲(chǔ)是寫模式,這是信息的權(quán)威來(lái)源。一個(gè) CQRS 為基礎(chǔ)的系統(tǒng)的讀取模型提供數(shù)據(jù)的物化視圖,通常是高度非規(guī)范化的意見。這些視圖量身定做的接口和應(yīng)用程序,這有助于最大程度地顯示和查詢性能的顯示要求。
使用事件作為寫入存儲(chǔ)區(qū),而不是實(shí)際的數(shù)據(jù)的流,在一個(gè)時(shí)間點(diǎn),避免了在單個(gè)聚合更新沖突并最大限度地提高性能和可擴(kuò)展性。該事件可用于異步生成用于填充讀取存儲(chǔ)器中的數(shù)據(jù)的實(shí)體化視圖。
由于事件存儲(chǔ)是信息的權(quán)威來(lái)源,就可以刪除物化視圖和回放所有過去的事件來(lái)創(chuàng)建當(dāng)前狀態(tài)的一個(gè)新表示當(dāng)系統(tǒng)升級(jí)時(shí),或者當(dāng)讀取模式必須改變。物化視圖是有效的數(shù)據(jù)的耐用只讀緩存。
當(dāng)使用 CQRS 結(jié)合事件獲取模式,考慮以下幾點(diǎn):
注意: 欲了解更多信息,請(qǐng)參閱活動(dòng)采購(gòu)模式和物化視圖模式,以及模式與實(shí)踐指導(dǎo) CQRS 之旅 MSDN 上。尤其是你應(yīng)該閱讀的章節(jié)介紹采購(gòu)活動(dòng)進(jìn)行全面的探索模式,以及它如何與 CQRS 有用的,而章 CQRS 和 ES 深潛了解更多,包括如何聚集分區(qū)可以在微軟的 Azure CQRS 使用。
下面的代碼顯示了一個(gè) CQRS 實(shí)現(xiàn),它使用不同的定義讀取和寫入模型為例某些提取物。該模型的接口沒有規(guī)定的基礎(chǔ)數(shù)據(jù)存儲(chǔ)的任何功能,并且可以發(fā)展和進(jìn)行微調(diào)獨(dú)立,因?yàn)檫@些接口是分開的。
下面的代碼演示了讀取的模型定義。
// 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 類來(lái)處理由應(yīng)用程序發(fā)出的命令。客戶端通常通過消息傳送系統(tǒng)發(fā)送命令到域,如一個(gè)隊(duì)列。命令處理程序接受這些命令,并調(diào)用域接口的方法。每個(gè)命令的粒度被設(shè)計(jì)成減輕沖突請(qǐng)求的機(jī)會(huì)。下面的代碼顯示了 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 接口包含在域中的意義的方法。通常情況下,在一個(gè) CRUD 環(huán)境中,這些方法將有通用名稱,如保存或更新,并有一個(gè) DTO 作為唯一的參數(shù)。該 CQRS 方法可以更好地定制,以滿足該組織開展業(yè)務(wù)及庫(kù)存管理的方式。
更多建議: