What is CQRS CQRS means Command Query Responsibility Segregation. Many people think that CQRS is an entire architecture, but they are wrong. CQRS is j ...
What is CQRS
CQRS means Command Query Responsibility Segregation. Many people think that CQRS is an entire architecture, but they are wrong. CQRS is just a small pattern. This pattern was first introduced by Greg Young and Udi Dahan. They took inspiration from a pattern called Command Query Separation which was defined by Bertrand Meyer in his book “Object Oriented Software Construction”. The main idea behind CQS is: “A method should either change state of an object, or return a result, but not both. In other words, asking the question should not change the answer. More formally, methods should return a value only if they are referentially transparent and hence possess no side effects.” (Wikipedia) Because of this we can divide a methods into two sets:
- Commands - change the state of an object or entire system (sometimes called as modifiers or mutators).
- Queries - return results and do not change the state of an object.
CQRS意味著命令與查詢的責任分離。 很多人認為CQRS是整體架構,但他們錯了。CQRS只是一個設計模式。 這種模式是首先由Greg Young和Udi Dahan提出。他們的靈感來源於Bertrand Meyer的書"Object Oriented Software Construction"中所定義命令查詢分離的模式。在CQS的主要理念就是是:"方法應該可以更改對象的狀態或者返回結果,但不能同時兼具。換句話說,問問題的時候就不應該變更答案。 更正式點,方法僅僅在它們是顯示引用並且沒有其它作用時返回一個值。"(維基百科),因為這種方法我們可以劃分為兩個設置:
- 命令 -改變對象或整個系統的狀態(有時稱為修飾符或賦值函數)。
- 查詢 -返回結果,並不會改變對象的狀態。
In a real situation it is pretty simple to tell which is which. The queries will declare return type, and commands will return void. This pattern is broadly applicable and it makes reasoning about objects easier. On the other hand, CQRS is applicable only on specific problems.
在實際環境中它是非常簡單的分辨出哪個是哪個。查詢將聲明返回類型,而命令將返回void。這種模式是廣泛適用的,它使對象的推理更容易。另一方面,CQRS只適用於特定的問題。
Many applications that use mainstream approaches consists of models which are common for read and write side. Having the same model for read and write side leads to a more complex model that could be very difficult to be maintained and optimized.
許多應用程式使用主流方法包括對models通常的讀和寫操作。對相同的model的讀取和寫入會導致一個更複雜的模型,可能很難被維護和優化.
The real strength of these two patterns is that you can separate methods that change state from those that don't. This separation could be very handy in situations when you are dealing with performance and tuning. You can optimize the read side of the system separately from the write side. The write side is known as the domain. The domain contains all the behavior. The read side is specialized for reporting needs.
這兩種模式的真正作用是你不能用分開的方法改變他們狀態。當你正在處理的性能和調優的情況下可能是非常方便,你可以分開寫入端去優化系統的讀取端。寫入端被作為一個領域(domain)。 這個領域包含的所有行為。讀取端是專門用於報告需求.
Another benefit of this pattern is in the case of large applications. You can split developers into smaller teams working on different sides of the system (read or write) without knowledge of the other side. For example developers working on read side do not need to understand the domain model.
這種模式的另一個好處是在大型的應用程式的情況下,您可以分割成更小的團隊開發工作(讀或寫),而不需要關註另一側的工作。例如讀取端的開發人員不需要瞭解領域域模型。
Query side
The queries will only contain the methods for getting data. From an architectural point of view these would be all methods that return DTOs that the client consumes to show on the screen. The DTOs are usually projections of domain objects. In some cases it could be a very painful process, especially when complex DTOs are requested.
Using CQRS you can avoid these projections. Instead it is possible to introduce a new way of projecting DTOs. You can bypass the domain model and get DTOs directly from the data storage using a read layer. When an application is requesting data, this could be done by a single call to the read layer which returns a single DTO containing all the needed data.
Query 端
查詢將只包含獲取數據的方法。從架構的角度來看,這些將是在屏幕上顯示客戶消費返回DTOs(數據傳遞對象)的所有方法。DTOs直接來自使用一個read層的數據存儲。在某些情況下,它可能是一個非常痛苦的過程,尤其是當要求複雜的DTO。
使用CQRS你能避免這些預測。相反,它是可以引入一個新的DTO的投影方式。您可以繞過域模型和DTO的直接使用讀出層從數據存儲。當一個應用程式請求數據,這可能是由一個單向的調用read層返回單個包含所有需要的數據DTO。
The read layer can be directly connected to the database (data model) and it is not a bad idea to use stored procedures for reading data. A direct connection to the data source makes queries very easy to by maintained and optimized. It makes sense to denormalize data. The reason for this is that data is normally queried many times more than the domain behavior is executed. This denormalization could increase the performance of the application.
read層可以直接連接到資料庫(數據模型),並使用存儲過程讀取數據並不算一個爛想法。直接連接到數據源的查詢非常容易維護和優化,這樣做的原因是,數據通常是查詢執行多次超過了領域的行為,這種非規範化可以提高應用程式的性能,非規範化的數據是有道理的。
Command side
Since the read side has been separated the domain is only focused on processing of commands. Now the domain objects no longer need to expose the internal state. Repositories have only a few query methods aside from GetById
.
命令端
由於讀取端已經被分開,doamin(也就是寫入端)只關註處理命令。現在的領域對象對象不再需要公開的內部狀態。儲存庫除了除了GetById
只有幾個查詢方法。
Commands are created by the client application and then sent to the domain layer. Commands are messages that instruct a specific entity to perform a certain action. Commands are named like DoSomething (for example, ChangeName, DeleteOrder ...). They instruct the target entity to do something that might result in different outcomes or fail. Commands are handled by command handlers.
命令通過客戶端應用程式創建然後發送到domain層.命令就是指示特定的實體來執行特定的動作的消息。命令被命名為像DoSomething (例如,ChangeName,DeleteOrder ...)。他們指示目標實體去做一些可能會導致不同的結果或者失敗的事情。命令集 command handlers操作。
public interface ICommand { Guid Id { get; } } public class Command : ICommand { public Guid Id { get; private set; } public int Version { get; private set; } public Command(Guid id,int version) { Id = id; Version = version; } } public class CreateItemCommand:Command { public string Title { get; internal set; } public string Description { get;internal set; } public DateTime From { get; internal set; } public DateTime To { get; internal set; } public CreateItemCommand(Guid aggregateId, string title, string description,int version,DateTime from, DateTime to) : base(aggregateId,version) { Title = title; Description = description; From = from; To = to; } }
All commands will be sent to the Command Bus which will delegate each command to the command handler. This demonstrates that there is only one entry point into the domain. The responsibility of the command handlers is to execute the appropriate domain behavior on the domain. Command handlers should have a connection to the repository to provide the ability to load the needed entity (in this context called Aggregate Root) on which behavior will be executed.
所有命令將被髮送到命令匯流排,它委托Command handler處理每一個Command 。這表明,領域(domain)只有一個入口點。Command handlers應該有一個連接到存儲庫提供載入所需的實體的能力(在這種上下文稱為聚合根)。
public interface ICommandHandler<TCommand> where TCommand : Command { void Execute(TCommand command); } public class CreateItemCommandHandler : ICommandHandler<CreateItemCommand> { private IRepository<DiaryItem> _repository; public CreateItemCommandHandler(IRepository<DiaryItem> repository) { _repository = repository; } public void Execute(CreateItemCommand command) { if (command == null) { throw new ArgumentNullException("command"); } if (_repository == null) { throw new InvalidOperationException("Repository is not initialized."); } var aggregate = new DiaryItem(command.Id, command.Title, command.Description, command.From, command.To); aggregate.Version = -1; _repository.Save(aggregate, aggregate.Version); } }
The command handler performs the following tasks:
- It receives the Command instance from the messaging infrastructure (Command Bus)
- It validates that the Command is a valid Command
- It locates the aggregate instance that is the target of the Command.
- It invokes the appropriate method on the aggregate instance passing in any parameter from the command.
- It persists the new state of the aggregate to storage.
命令處理程式執行以下任務:
- 從messaging infrastructure(命令匯流排)接收命令實例。
- 驗證該命令是否是一個有效的命令
- 命令的目標是聚合實例(aggregate instance)。
- 從command對象中傳遞任意參數調用作用在聚合實例上的適當的方法。
- 存儲aggregate 的新狀態繼續存在。
Internal Events
The first question we should ask is what is the domain event. The domain event is something that has happened in the system in the past. The event is typically the result of a command. For example the client has requested a DTO and has made some changes which resulted in a command being published. The appropriate command handler has then loaded the correct Aggregate Root and executed the appropriate behavior. This behavior raises an event. This event is handled by specific subscribers. Aggregate publishes the event to an event bus which delivers the event to the appropriate event handlers. The event which is handled inside the aggregate root is called an internal event. The event handler should not be doing any logic instead of setting the state.
內部事件
我們的第一個問題應該問什麼是域事件。域事件是指在系統中過去已經發生的一些事件。事件通常是一個命令的結果。例如,客戶端請求一個DTO做出一些的變化,導致被公佈在命令中。然後相應的命令處理程式載入正確的Aggregate root並執行適當的行為。這種行為引發一個事件。此事件是由特定的用戶處理的。Aggregate 發佈事件到事件匯流排併為該事件分派適當的處事件。這是中內部操作聚合根的事件被稱為內部事件。該事件處理程式不應該做任何邏輯替狀態設置。
Domain Behavior
域行為
public void ChangeTitle(string title) { ApplyChange(new ItemRenamedEvent(Id, title)); }
Domain Event
域事件
public class ItemCreatedEvent:Event { public string Title { get; internal set; } public DateTime From { get; internal set; } public DateTime To { get; internal set; } public string Description { get;internal set; } public ItemCreatedEvent(Guid aggregateId, string title , string description, DateTime from, DateTime to) { AggregateId = aggregateId; Title = title; From = from; To = to; Description = description; } } public class Event:IEvent { public int Version; public Guid AggregateId { get; set; } public Guid Id { get; private set; } }
Internal Domain Event Handler
內部域事件處理程式
public void Handle(ItemRenamedEvent e) { Title = e.Title; }
Events are usually connected to another pattern called Event Sourcing (ES). ES is an approach to persisting the state of an aggregate by saving the stream of events in order to record changes in the state of the aggregate.
事件通常是連接到另一個叫做 Event Sourcing(ES)模式。ES是一種通過事件流保持保存aggregate狀態,以記錄更改aggregate狀態的方法。
As I mentioned earlier, every state change of an Aggregate Root is triggered by an event and the internal event handler of the Aggregate Root has no other role than setting the correct state. To get the state of an Aggregate Root we have to replay all the events internally. Here I must mention that events are write only. You cannot alter or delete an existing event. If you find that some logic in your system is generating the wrong events, you must generate a new compensating event correcting the results of the previous bug events.
正如我前面提到的,的每一個Aggregate Root狀態的變化都是由事件觸發的內部事件操作Aggregate Root除了設置正確的狀態沒有其他作用。為了得到一個Aggregate Root的狀態,我們不得不重播所有內部的事件。在這裡我必須提到,事件是只寫的。你不能改變或刪除現有的事件。如果您發現您的系統中產生一些邏輯錯誤的事件,您必鬚生成一個新的補償事件,糾正以前的錯誤事件的結果。
External Events
External events are usually used for bringing the reporting database in sync with the current state of the domain. This is done by publishing the internal event to outside the domain. When an event is published then the appropriate Event Handler handles the event. External events can be published to multiple event handlers. The Event handlers perform the following tasks:
- It receives an Event instance from the messaging infrastructure (Event Bus).
- It locates the process manager instance that is the target of the Event.
- It invokes the appropriate method of the process manager instance passing in any parameters from the event.
- It persists the new state of the process manager to storage.
But who can publish the events? Usually the domain repository is responsible for publishing external events.
外部事件
外部事件通常用於報告資料庫當前同步域的狀態。他所做的就是發佈內部事件到外域。當事件被髮布時相應的事件處理程式處理該事件。外部事件可以發佈到多個事件處理程式。事件處理程式執行以下任務:
- 從messaging infrastructure收到一個事件實例(事件匯流排)。
- 事件的目標是定位進程管理器實例。
- 進程管理器實例從事件中傳遞何任意參數調用適當的方法。
- 繼續存儲進程管理器的新狀態。
可是,是誰發佈的事件的?通常情況下,領域倉庫負責外部事件發佈。
剛瀏覽下這書,感覺蠻不錯的,比《Pro asp.netMVC3》質量要好很多,沒學過的童鞋可以每天學習1到2章,看不懂的問題在群論壇留言,大家積极參与,以後群里就以MVC為主要話題。