1、分離查詢命令 Separating commands from queries 數據庫
早期的面向DDD設計方法的難點是如何設計一個類,這個類要包含域的方方面面。一般來講,任務軟件系統方法調用能夠分爲兩類:查詢和命令。在這裏,查詢是指一個系統的和個操做,它不會改變系統的任務值,僅返回一些結果。命令的職責是個性系統數據。 若是兩組方法都使用相同的域模型,邏輯上可能存在查詢和命令分離不明顯問題,因此引入新的設計模式。 後端
從某種程序度上,CQRS是複雜的域模型設計的一種橫向設計思惟。若是域模型客觀上就是複雜的,咱們使用CQRS是否還須要? 從目前的設計理念看來,CQRS使用的是兩個不一樣的域模型,而不是一個。分離是經過將查詢和命令分離到兩個層上,它們各有本身的架構體系和服務集合。 設計模式
命令和查詢的簡單對系統的設計影響可能讓人驚訝。系統的組織做爲兩個並行分支,如圖上圖 所示的體系結構,有一個正式的域模型是系統的嚴格要求。 緩存
在提供查詢服務的時候咱們可能不須要域模型,查詢僅僅是用記接口用記查詢數據的方法。查詢的結果中有可能有命令,因此域模型中設計的各類關係和約束都是不必的。域模型的查詢區域專門定製簡單的數據傳輸類DTO作爲數據載體。這種狀況下,域服務可能成爲使用弱類來實現業務有的某些功能。 網絡
不一樣 於DDD,CQRS 不是企業級系統設計的綜合方法。CQRS是爲你在大型系統中爲上下文邊界設計的一個指導模式。DDD設計基本統一語言,貫穿於整個系統設計。 架構
-簡單化設計 Simplifcation of the design dom
在域模型交互設計中,一般要對面系統的複雜點是改變系統狀態的操做。命令須要驗證當前狀態並決定是否執行,命令同步要保持系統數據的一致性。在讀寫共享相同的數據實體時很難確保一個操做不會作出意料以外的讀寫操做。早期咱們就認識到模型的命令複雜度以笛卡爾積式增加的。稱N爲查詢和命令的複雜度。在單個域模型中,在查詢的規定和約束影響命令和反之亦然,如同笛卡爾積,複雜度的成長爲N*N。若是將查詢和命令分離,模型的複雜度則爲N+N。 異步
-加強可擴展性的潛力 Potential for enhanced scalability ide
可擴展性有不少方面的因素,針對性解決方法是保持每個系統的惟一性。一般,可擴展性指系統的可維護性以及在用戶增加數量級下的性能。架構 的可擴展性取決於大多數方法的操做類型,若是讀取是主要的操做類型,能夠引入緩存完全解決數據庫的讀取壓力。若是是寫操做將系統拖慢,應考慮使用異步寫替代同步,或使用隊列。讀寫分離後,對系統的可擴展性能夠更容易、更有針對性的處理。 性能
CQRS實際上沒有什麼負面影響,如何使用CQRS取決你如何理解CQRS,目前來講它只用於一種模式,在兩個不一樣的層,一個是查詢服務層一個是命令服務層。系統中全部的部分就會由CQRS帶來益處,而且不須要太多的學習成本。
只讀域模型 The read domain model
一個只用於讀的域模型要比讀寫兼備的域模型簡單的多。有下面一個問題,一個Order類中有一個產品類項列表屬性。該屬性本質上包含可枚舉的數據,但不知道Items應該是哪一種類型,第一種方法是使用泛型IList<T>,這種方法能夠實現:
public IList<OrderItem> Items { get; private set; }
使用ReadOnly屬性是更好的先擇。Read-only不容許更改集合的結構,此外,若是做爲包裝器,用於常規的列表建立只讀集合,則對基礎列表的更改不影響只讀包裝
public class Order
{
private readonly IList<OrderItem> _items;
public Order()
{
_items = new List<MOrderItem>();
}
public ReadOnlyCollection<OrderItem> Items
{
get
{
return new ReadOnlyCollection<OrderItem>(_items);
}
}
public void Add(int id, int quantity)
{
_items.Add(new OrderItem(id, quantity));
}
}
public class OrderItem
{
public OrderItem(int id, int quantity)
{
Quantity = quantity;
ProductId = id;
}
public int Quantity { get; /*private*/ set; }
public int ProductId { get; /*private*/ set; }
}
設計只讀模型
查詢堆棧可能仍然須要域服務從存儲中提取數據,併爲它服務達應用程序和表示層。在這種狀況下,域名服務和專門的存儲庫,應將重定向容許只讀取的操做在存儲上。在這種狀況下,域名服務和專門的存儲庫應將重定向容許只讀取的操做在存儲上。
……………………………………
的CQRS場景下,Command是的惟一做用就是改變系統的數據。一般應用層接受來處表現層的數據並執行。命令是針對後端,如註冊一個新用戶、 處理購物車的內容或更新的客戶配置文件等數據落地操做。CQRS 的角度來看,命令就是數據持久化的單向操做。
任務有兩種方式被觸發,一種是用戶在UI上明確的開始一項任務,別一種是由系統的一些服務自動觸發的任務。命令的主要任務是更新系統數據,但有時候調用都須要返回一些數據來確認調用是否成功。
有兩種類型的消息:命令和事件。兩種類型中命令是一種數據包,命令是系統執行請求的必要數據。它們有相同點也有不一樣點
命令由調用者直接發出
命令能夠被系統駁回
命令可能會執行失敗
基於網絡的命令會依賴系統的當前狀態
事件不能由系統駁回或取消
事件能夠有多外處理者
The processing of an event can, in turn, generate other events for other handlers to process.
An event can have subscribers located outside the bounded context from which it originated
事件類寫法,以下面的代碼,命令和事件都繼承自Message類。
public class CheckoutCommand : Message
{
public string CartId { get; private set; }
public string CustomerId { get; private set; }
public CheckoutCommand(string cartId, string customerId)
{
CartId = cartId;
CustomerId = customerId;
}
}
Conversely, here's the layout of an event class.
public class DomainEvent : Message
{
// Common properties
...
}
public class OrderCreatedEvent : DomainEvent
{
public string OrderId { get; private set; }
public string TrackingId { get; private set; }
public string TransactionId { get; private set; }
public OrderCreatedEvent(string orderId, string trackingId, string transactionId)
{
OrderId = orderId;
TrackingId = trackingId;
TransactionId = transactionId;
}
}
命令與事件處理
命令由一個被稱爲Command bus的處理者來管理。事件由Event bus組件來管理。有此時候命令和事件由同一個bus來處理。下圖是基於一個事件的CQRS解決方案。全部的任務都是由用戶接口發起,在Asp.net MVC中Controller中的Action接收請求並嚮應用層發起命令。
Bus 組件
Command Bus持有一系統已知業務處理器,這些處理器能夠有命令來觸發。事件的處理同時會在域中產生許多事件。生成的事件被髮布到同一個命令bus或Event bus。Comand Bus是一個接收消息而且找出執行方法的單獨的類,Bus類不會本身執行實際要處理的任務,它會選擇一個已註冊的處理者來處理事件或命令。
public interface IHandles
{
void Handle(T message);
}
接口同時處理命令和事件
public class Bus
{
private static readonly Dictionary<Type, Type> SagaStarters =
new Dictionary<Type, Type>();
private static readonly Dictionary<string, object> SagaInstances =
new Dictionary<string, object>();
public static void RegisterSaga<TStartMessage, TSaga>()
{
SagaStarters.Add(typeof(TStartMessage), typeof(TSaga));
}
public static void Send<T>(T message) where T : Message
{
// Publish the event
if (message is IDomainEvent)
{
// Invoke all registered sagas and give each
// a chance to handle the event.
foreach (var saga in SagaInstances)
{
var handler = (IHandles<T>)saga;
if (handler != null)
handler.Handle(message);
}
}
// Check if the message can start one of the registered sagas
if (SagaStarters.ContainsKey(typeof(T)))
{
// Start the saga creating a new instance of the type
var typeOfSaga = SagaStarters[typeof(T)];
var instance = (IHandles<T>)Activator.CreateInstance(typeOfSaga);
instance.Handle(message);
// At this point the saga has been given an ID;
// let's persist the instance to a (memory) dictionary for later use.
var saga = (SagaBase)instance;
SagaInstances.Add(saga.Data.Id, instance);
return;
}
// The message doesn't start any saga.
// Check if the message can be delivered to an existing saga instead
if (SagaInstances.ContainsKey(message.Id))
{
var saga = (IHandles<T>)SagaInstances[message.Id];
saga.Handle(message);
// Saves saga back or remove if completed
if (saga.IsComplete())
SagaInstances.Remove(message.Id);
else
SagaInstances[message.Id] = saga;
}
}
}
Bus的功能就是作命令映射和事件分發。
Saga組件
通常狀況下,一個Saga組件看起來像邏輯上相關的方法和事件處理程序的集合。每一個Saga是一個組件,它聲明瞭如下信息: