2016 年 12 月

第 31 卷,第 13 期

本文章是由機器翻譯。

技術最前線 - 使用事件及 CQRS 重寫 CRUD 系統

Dino Esposito

Dino Esposito世界的完整的傳統的建立、 讀取、 更新、 刪除 (CRUD) 系統根據關聯式資料庫及填滿的商務邏輯區塊,有時候被埋在預存程序與有時救黑箱元件中。這類黑色方塊的核心是 CRUD 四個操作︰ 建立新的實體、 讀取、 更新和刪除。在一個相當高的抽象層,這正是它的 — 任何系統是,在某種程度的 CRUD 系統。實體可能是相當複雜的情況下,並採用更多的彙總形式。

在 Domain-Driven 設計 (DDD),彙總是與根物件的實體的商務相關叢集中。因此,建立、 更新或甚至刪除實體可能受到許多複雜的商務規則。是通常有問題,大部分是基於 UX 甚至讀取狀態的彙總。將改變系統狀態的模型不一定適用於呈現資料給所有使用者使用案例的相同模型。

採取到其最高的抽象層級的 CRUD 通往直接分隔什麼改變從功能只會傳回它的一個或多個檢視系統狀態。這是未經處理的命令與查詢責任隔離 (CQRS),也就是很棒的命令與查詢責任隔離本質。

不過,還有其他的軟體架構設計人員和開發人員必須列入考量。系統的狀態改變命令堆疊,並在具體而言,第一次建立彙總。這也是在稍後更新及刪除相同的彙總。而這是精確的點重新思考。

歷程記錄,就幾乎所有的軟體系統的關鍵。支援持續的業務撰寫軟體時,過去教訓是重要的原因有二︰ 若要避免遺失單一的事情發生,並改善客戶和員工的服務。

5 月 2016 (msdn.com/magazine/mt703431) 和 2016 年 6 月 (msdn.com/magazine/mt707524) 期專欄中的,我曾介紹如何將傳統的 CRUD 延伸至歷程記錄的 CRUD。我年 8 月 2016 (msdn.com/magazine/mt767692) 和 2016 年 10 月 (msdn.com/magazine/mt742866) 資料行,相反地,我曾介紹事件-命令-傳奇 (ECS) 模式和名冊 FX 架構 (bit.ly/2dt6PVD) 作為新的方式來表示商務邏輯的日常需求的建置組塊。

在這個資料行,接下來,我將探討應用 CQRS 與事件來源與重寫預約示範應用程式 (相同我在 5 月和年 6 月的資料行中使用) 保留歷程記錄在系統中的兩個先前提及的優點。

綜觀全貌

我的範例應用程式是內部的預約系統會議室。主要使用案例是捲動行事曆和線上叢書 》 的一或多個可用的插槽,給定的空間上的登入的使用者。系統管理的實體,例如會議室、 RoomConfiguration 與預約,,您可以想像,在概念上整個應用程式即將加入及編輯聊天室和組態 (也就是當空間開啟時對預約和長度的單一位置),以及加入、 更新和取消保留項目。[圖 1提供的系統上的使用者所執行的動作,就會將它們架構 CQRS 系統根據 ECS 模式中的方式。

使用者動作和系統的高階設計
[圖 1 使用者動作和系統的高階設計

使用者可以輸入新的保留項目、 移動和取消,並甚至簽入的空間,讓系統知道實際使用保留的空間。每個動作後面的工作流程以處理的傳奇和傳奇是命令堆疊中定義的類別。傳奇類別組成處理常式方法,每個處理命令或事件。放置保留區 (或移動現有的保留項目) 的命令堆疊推送命令。一般而言,發送命令可以直接叫用對應的傳奇方法一樣簡單或匯流排的服務來進行。

若要保留歷程記錄,您需要追蹤的任何處理命令至少所有商務影響。在某些情況下,您也可能想要追蹤的原始命令。命令會執行部分的資料傳輸物件的輸入資料。執行命令,以透過傳奇商務影響為事件。事件是資料傳輸物件執行完整描述事件的資料。事件會儲存在特定資料存放區。事件所使用的儲存體技術沒有嚴格限制。它可以是純文字的關聯式資料庫管理系統 (RDBMS) 或 NoSQL 資料存放區。(參閱 MementoFX 和 RavenDB 匯流排的安裝程式的 10 月資料行)。

協調命令與查詢

讓我們假設使用者將命令登記在給定的空間上的位置。在 ASP.NET MVC 案例中,控制器會取得張貼的資料,並將匯流排命令。匯流排的服務設定為可辨識少數巨作,每個傳奇宣告命令 (和/或事件) 來處理它所需要的。因此,匯流排會將訊息分派至傳奇。傳奇的輸入是 UI 表單中,輸入使用者的未經處理資料。傳奇處理常式負責接收來開啟與商務邏輯一致的彙總的執行個體中的資料。

假設使用者按一下活頁簿,如所示**[圖 2**。在按鈕所觸發的控制器方法接收房間、 日期和時間和使用者名稱的識別碼。傳奇處理常式必須將這變成預約彙總量身訂做對預期的商務邏輯處理。商務邏輯將於合理範圍內解決的權限、 優先順序、 成本和甚至純文字的並行存取的區域中的問題。不過,起碼傳奇方法具有建立預約彙總,並將它儲存。

預訂會議室中範例系統
[圖 2 範例系統中預訂會議室

在第一個可見,程式碼片段中**[圖 3**與使用的處理站和未處理的儲存機制屬性除了一般 CRUD 並無不同。設定的事件存放區中寫入的聯合的影響 factory 和儲存機制觸發預約類別的實作中的所有事件。

[圖 3 類別結構的傳奇

public class ReservationSaga : Saga,
  IAmStartedBy<MakeReservationCommand>,
  IHandleMessages<ChangeReservationCommand>,
  IHandleMessages<CancelReservationCommand>
{
   ...
  public void Handle(MakeReservationCommand msg)
  {
    var slots = CalculateActualNumberOfSlots(msg);
    var booking = Booking.Factory.New(
      msg.FullName, msg.When, msg.Hour, msg.Mins, slots);
    Repository.Save(booking);
  }
}

最後,儲存機制不會儲存記錄,預約類別的目前狀態,以某種方式對應至資料行的屬性。它只會將商務事件儲存至存放區並在這個階段結束,您知道確實發生了什麼問題您預約 (建立時間和方式),但您不需要的所有傳統資訊準備向使用者顯示。您知道已發生的問題,但您沒有可以顯示任何項目。工廠原始碼所示**[圖 4**。

[圖 4 工廠原始碼

public static class Factory
{
  public static Booking New(string name, DateTime when,
    int hour, int mins, int length)
  {
    var created = new NewBookingCreatedEvent(
      Guid.NewGuid(), name.Capitalize(), when,
      hour, mins, length);
    // Tell the aggregate to log the "received" event
    var booking = new Booking();
    booking.RaiseEvent(created);
    return booking;
  }
}

工廠接觸到預約類別的新建立的執行個體的任何屬性,但事件類別會建立並填入與實際的資料儲存在執行個體,包括的首字大寫的名稱,以及客戶將會永久追蹤整個系統的保留區的唯一識別碼。事件會傳遞至 RaiseEvent 方法,MementoFX 架構,因為它是所有彙總的基底類別。RaiseEvent 會將事件加入至儲存機制將詳細解說 「 儲存 」 的彙總執行個體時的內部清單。我使用 「 儲存 」,就會發生什麼事,因為一詞,但是我其前後放置引號來強調,它是比傳統的 CRUD 中的動作類型不同。儲存機制儲存事件的保留區已建立使用指定的資料。更精確地說,在儲存機制儲存商務工作流程,也就是傳奇處理常式方法,在執行期間記錄的彙總執行個體的所有事件中所示**[圖 5**。

儲存與儲存狀態的事件
[圖 5 儲存與儲存狀態的事件

但是追蹤命令所產生的商務事件是不足夠。

反正規化查詢堆疊的事件

如果您看一下 CRUD 透過保留資料記錄的情況下,您可以看到,建立和讀取實體不會影響記錄,但也如此更新和刪除。執行事件存放區是附加專用,因此更新和刪除相同的彙總相關的只是新事件。具有指定的彙總,事件的清單,告訴您的所有項目以外的目前狀態歷程記錄。而目前的狀態也會呈現給使用者所需。

以下是 denormalizers 適用於什麼情況。Denormalizer 是建置為事件處理常式,就像這些上所儲存的事件存放區的集合類別。向匯流排登錄 denormalizer 和匯流排分派給它的事件,每次取得。最後的結果會寫入至接聽預約建立事件 denormalizer 有機會回應一個觸發時。

Denormalizer 事件中取得的資料,並沒有需要的任何內容以執行動作,例如,保留簡易查詢關聯式資料庫與記錄的事件同步。關聯式資料庫 (或 NoSQL 存放區或快取,如果是更簡單或更有幫助使用) 屬於查詢堆疊,其 API 不提供對預存的事件清單的存取權。除此之外,您可以建立相同的原始事件的特定檢視的多個 denormalizers。(我會深入探究這方面下一篇專欄。) 在**[圖 1**,維護與事件同步 denormalizer 動作一般關聯式資料庫中會填入行事曆的使用者會挑選一個位置。請參閱**[圖 6** denormalizer 類別程式碼。

[圖 6 Denormalizer 類別結構

public class BookingDenormalizer :
  IHandleMessages<NewBookingCreatedEvent>,
  IHandleMessages<BookingMovedEvent>,
  IHandleMessages<BookingCanceledEvent>
{
  public void Handle(NewBookingCreatedEvent message)
  {
    var item = new BookingSummary()
    {
      DisplayName = message.FullName,
      BookingId = message.BookingId,
      Day = message.When,
      StartHour = message.Hour,
      StartMins = message.Mins,
      NumberOfSlots = message.Length
    };
    using (var context = new MfxbiDatabase())
    {
      context.BookingSummaries.Add(item);
      context.SaveChanges();
    }  }
  ...
}

使用 reference 為**[圖 5**,denormalizers 提供關聯式 CRUD 僅供讀取。Denormalizers 輸出通常稱為 「 讀取的模型 」。 讀取模型中的實體通常不符合用來產生事件,因為它們大部分驅動 UI 的需求彙總。

更新和刪除

假設現在使用者想要移動先前登記的位置。命令放置的新位置的所有詳細資料,並傳奇方法會負責撰寫針對給定的預約已移動的事件。傳奇來擷取彙總,並且需要更新的狀態中。如果 denormalizers 剛建立關聯式 (因此讀取的模型幾乎伴隨網域模型) 的彙總狀態的複本,您可以從該處取得更新的狀態。否則,您會建立全新的彙總,並在其上執行所有記錄的事件。在重新執行結束時,彙總都處於最更新的狀態。重新執行事件不是您必須直接執行的工作。MementoFX 取得更新彙總傳奇處理常式內的程式碼行︰

var booking = Repository.GetById<Booking>(message.BookingId);

接下來,您將套用至執行個體所需的任何商務邏輯。商務邏輯會產生事件和事件會保存到儲存機制︰

booking.Move(id, day, hour, mins);
Repository.Save(booking);

如果您使用 「 網域模型 」 模式,並遵循 DDD 原則,Move 方法會包含所有網域邏輯和事件。否則,函式執行任何商務邏輯並直接引發事件,以在匯流排。繫結 denormalizer 另一個事件處理常式,您有機會更新讀取的模型。

方法無法取消登記不同。取消登記的事件是商務事件,且必須追蹤。這表示您可能想要有布林值屬性的彙總來執行邏輯的刪除。在讀取模式中,不過,刪除可能視而不見實體根據您的應用程式是否要查詢已取消預約的讀取的模型而定。有趣的副作用是,您可以一律重建讀取的模型重新執行事件,從一開始,或從復原點。它只要是建立使用事件存放區 API 來讀取事件,以及直接呼叫 denormalizers 臨機操作工具。

使用事件存放區 API

看看的下拉式清單中選取**[圖 2**。使用者想要拉長預約只要從 [開始時間。商務邏輯的彙總必須是可以找出原因,並以其必須存取的預約清單中的開始時間晚於相同日期執行。這是在傳統的 CRUD,沒什麼大不了但 MementoFX 可讓您查詢的事件,以及︰

var createdEvents = EventStore.Find<NewBookingCreatedEvent>(e =>
  e.ToDateTime() >= date).ToList();

程式碼片段會傳回一份 NewBookingCreated 事件之後指定的時間。不過,有不保證會建立預約仍在作用中並沒有已移至另一個位置。您真的需要取得這些彙總的更新的狀態。演算法是由您決定。比方說,您可以篩選掉從清單中建立事件不再作用預約,然後取得剩餘的預約的識別碼。最後,您可以檢查針對您想要避開重疊延伸的實際位置。在本文的原始程式碼,我撰寫程式碼命令堆疊中的個別 (網域) 服務中所有邏輯。

總結

使用應用 CQRS 與事件來源已不再侷限於特定系統的並行處理、 延展性和效能的高階需求。基礎結構使用,可讓您能使用彙總及工作流程,與任何現今 CRUD 系統可以重新撰寫方式帶來許多好處。這些優點包括︰

  • 保留資料的歷程記錄
  • 更有效率且更有彈性的方式來實作商務工作和變更工作反映商務變更具有有限的精力和風險的迴歸
  • 事件是不可變的事實,因為它們 trivial 複製,而且可以透過程式設計方式產生模型,在重複的甚至是讀取將

這表示 ECS 模式 (或有時稱為 CQRS/ES 如同) 具有延展性的可能性。更多,「 MementoFX 架構很有用,因為它可簡化一般工作,並提供彙總的抽象概念,簡單的程式設計。

MementoFX 推送 DDD 導向的方法,但您可以使用 ECS 模式與其他架構和其他功能的開發架構之類的典範。沒有更多的好處,而可能最相關的。我將告訴您它下一篇專欄中。


Dino Esposito是每個 「 Microsoft.NET:  架構的企業應用程式 」 (Microsoft Press,2014年) 和 [使用 ASP.NET 最新 Web 應用程式] (Microsoft Press,2016年)。.NET 和 Android 平台在 JetBrains,並在世界各地的產業活動的技術推廣人員 Esposito 分享軟體的願景software2cents.wordpress.com和 Twitter: @despos

感謝下列 Microsoft 技術專家來檢閱這份文件︰ Andrea Saltarello