CQRS

来源:https://www.cnblogs.com/rohelm/archive/2017/12/27/3176370.html
-Advertisement-
Play Games

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為主要話題。


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 原題是這樣的: 給出一個字元串數組S,找到其中所有的亂序字元串(Anagram)。如果一個字元串是亂序字元串,那麼他存在一個字母集合相同,但順序不同的字元串也在S中。 樣例 對於字元串數組 ["lint","intl","inlt","code"] 返回 ["lint","inlt","intl"] ...
  • Pandas基礎篇 Pandas基於Numpy開發,提供了很多高級的數據處理功能。 1、Pandas中的數據對象 Series和DataFrame是Pandas中最常用的兩個對象。 1.1 Series對象 是Pandas中最基本的對象,可用Numpy的數組處理函數直接對Series對象進行處理。支 ...
  • 許可權修飾符 許可權修飾符包括public、private、protected和不加任何修飾符的default,它們都可以修飾方法和變數。其中public和預設的default(不加任何修飾符)這兩個還可以修飾class。private和protected修飾類的情況只能在使用內部類時修飾,正常情況下不 ...
  • 類路徑(classpath) java編譯器編譯.java文件和java虛擬機執行.class文件時的路徑和寫法不一樣。 在沒有設置任何classpath環境變數的情況下,javac可以編譯全路徑的.java文件。例如: 編譯後,在.java同路徑目錄下生成class文件。 預設java虛擬機要從c ...
  • 基礎 類有屬性和方法,它們對本類有效(作用範圍)。類的屬性就是成員變數,它預設會賦值初始化。類的方法是類具有的一些行為。 類是抽象的,將它們實例化後就是對象(通過new進行實例化),各實例化後的對象都具有這些成員變數的屬性,且賦有具體的值,如果某對象沒有為成員變數賦值,則採用預設初始化時的值。每個n ...
  • 前面 lucene 初探 都是為了solr打基礎的. 雖然lucene 的filter 沒有涉及, 但是打基礎, 差不多夠用了. 一. solr 和 lucene 的區別 這裡我就用自己的理解來說了, 可能不全, 但是應該夠用了, 網上能搜到官方一點的. 首先, solr 是基於 lucene的. ...
  • lucene初探, 是為了後面solr做準備的. 如果跳過lucene, 直接去看solr, 估計有點懵. 由於時間的關係, lucene查詢方法也有多個, 所以單獨出來. 一. 精確查詢 在查詢的時候, 新建一個Term對象, 進去精確匹配. 前一篇提到過, 經過分詞器分下來的每一個詞或者一段話, ...
  • 把一個數據集List<T>複製至到另一個數據集List<T>。 方法一,可以使用迴圈,然後把每一個T添加至另一個集合中去: public void ListDemo() { var listA = new List<int> { 2, 5, 6, 8, 23, 56, 4 }; var listB ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...