2016 年 10 月

第 31 卷,第 10 期

本文章是由機器翻譯。

技術最前線 - 適用於商務邏輯的事件-命令-Saga 方法

Dino Esposito

Dino Esposito如果我們無法作用相當凍結的需求外,任何設計開發前完成的工作會確實付清。如果您記得 「 大設計向上面對 」 一詞,您會知道我的意思。如果沒有,您可以查看它在 bit.ly/1jVQr3g。處理兩者的完整網域模型許多複雜的工作流程和而且豐富的反正規化資料檢視需要可靠且穩定網域,而延長開發時間。換句話說,軟體有相關性今天給每天的商務,完整的網域模型是好到哪裡去現在靈活移動出現之前,已觀察得到大設計接最上層。

事件的命令-傳奇 (ECS) 方法會將升級更靈活的方式,來實作商務工作流程。它仍然需要穩固的知識的程序和商務規則,但它並不需要大的設計,以編寫開始之前先準備就緒。此外,它是靈活地配合變更,更重要的是,商務點,以及一開始會遺失或被忽略。

ECS 方法提升訊息為基礎的編寫的商務程序,這是非常接近流程圖的抽象層級。因此,它是專案關係人可以輕鬆地進行通訊和驗證。商務程序的訊息型編寫也很容易了解適用於開發人員,即使他們了解特定商務網域是有限。詞彙 ECS 可能會聽新,但它為基礎的概念是您可能會發現相同稱為 CQRS/ES 其他來源中。

在本月的專欄中,我將會呈現。特別是用來實作商務邏輯的應用程式使用較新的概念,例如命令和巨作以.NET 為基礎的架構。如本主題介紹,您可能要看看我 2016 年 9 月的專欄 (msdn.com/magazine/mt767692)。「 商務邏輯 」 一詞這裡包含應用程式和網域的邏輯。「 應用程式邏輯 」 是您用來實作所有相依於指定的前端的使用案例。相反地,「 定義域邏輯 」,是使用案例而異,因此您可能必須在展示層及應用程式層的所有層面可完全重複使用。

MementoFX 作用中

讓我們開始新的 ASP.NET MVC 專案已設定為使用通用的項目,例如啟動程序、 jQuery、 Entity Framework 和 ASP.NET SignalR。現在加入控制器類別與方法,並顯示給使用者以 HTML 格式的相關的檢視。當使用者提交表單時,下列程式碼所要執行︰

[HttpPost]
public ActionResult Apply(NewAccountRequestViewModel input)
{
  _service.ApplyRequestForNewBankAccount(input);
  return RedirectToAction("index", "home");
}

初看,這是標準的程式碼,但有趣位於下方的介面。在應用程式層,因此開啟 ApplyRequestForNewBankAccount 方法的程式碼。

Business-wise,應用程式使用者 (同樣地,銀行員工) 具有只要填寫透過客戶要求,以開啟新的帳戶。沒有特定的處理序啟動新要求傳入。您可以利用撰寫程式碼應用程式層中之工作流程權限的所有步驟,或您可以嘗試 ECS 方法。在後者的情況下,以下是什麼您想到了︰

public void ApplyRequestForNewBankAccount(NewAccountRequestViewModel input)
{
  var command = new RequestNewBankAccountCommand(
    input.FullName, input.Age, input.IsNew);
  Bus.Send(command);
}

RequestNewBankAccountCommand 類別是有點不只是單純舊 CLR 物件 (POCO) 類別。它是 POCO 類別,但它繼承自命令。命令類別會定義的其中一種會形成 MementoFX framework NuGet 封裝。您接著加入封裝中所示 [圖 1

安裝 MementoFX NuGet 封裝
[圖 1 MementoFX NuGet 封裝安裝

MementoFX 架構由三個主要部分組成︰ 核心程式庫、 事件存放區和匯流排。在範例組態中,我使用 RavenDB 內嵌的版本來儲存網域事件和記憶體中匯流排 (Postie) 應用程式層和巨作來發佈和訂閱的事件。如果您瀏覽進一步 NuGet 平台,也會發現基礎 Rebus 和事件存放區元件根據 MongoDB 的匯流排元件。現在,下列程式碼會編譯得很好︰

public class RequestNewBankAccountCommand : Command
{
  ...
}

MementoFX 目前的版本,在基底的命令類別是只標記,並包含任何功能的程式碼,但這很可能會變更在未來的版本。最後的商務程序的第一個步驟的輸入參數將命令。若要觸發商務程序,在匯流排放命令。

設定 MementoFX 環境

MementoFX 初始設定是如果您使用逆轉控制 (IoC) 架構,例如 Unity 更容易。若要設定 MementoFX,您需要執行下列三個動作︰ 首先,初始化事件存放區的選擇。第二,告訴 MementoFX 如何解析泛型介面型別 (這通常表示的匯流排類型的相關事項告訴架構使用的事件存放區和事件儲存文件) 的具象型別。第三,您實際上匯流排解析實體的執行個體,並繫結至巨作和適當的處理常式。[圖 2 摘要說明的程序。

[圖 2 設定 MementoFX

// Initialize the event store (RAVENDB)
NonAdminHttp.EnsureCanListenToWhenInNonAdminContext(8080);
var documentStore = new EmbeddableDocumentStore
{
  ConnectionStringName = "EventStore",
  UseEmbeddedHttpServer = true
};
documentStore.Configuration.Port = 8080;
documentStore.Initialize();
// Configure the FX
var container = MementoFxStartup
  UnityConfig<InMemoryBus, EmbeddedRavenDbEventStore,
     EmbeddableDocumentStore>(documentStore);
// Save global references to the FX core elements
Bus = container.Resolve<IBus>();
AggregateRepository = container.Resolve<IRepository>();
// Add sagas and handlers to the bus
Bus.RegisterSaga<AccountRequestSaga>();
Bus.RegisterHandler<AccountRequestDenormalizer>();

中所示 [圖 2, ,匯流排具有兩個 「 訂閱者 」 — 強制 AccountRequestSaga 和選擇性 AccountRequestDenormalizer。傳奇包含處理中的要求作業的程式碼。您可能會有任何商務邏輯也適用於此處。Denormalizer 會接收彙總的相關資訊,而且如有需要將會建立只供查詢資料的投影。

設計的傳奇

傳奇是代表商務程序的執行個體的類別。根據您使用的匯流排的實際功能傳奇都可以保存、 暫停並繼續視情況。您在 MementoFX 預設匯流排僅適用於在記憶體中。因此,任何傳奇是一次性的程序從開始到結束執行的交易。

傳奇必須起始事件或命令。表示透過介面 IAmStartedBy 入門訊息。任何其他訊息 (命令或事件) 的傳奇知道如何處理透過 IHandlesMessage 介面繫結︰

public class AccountRequestSaga : Saga,
  IAmStartedBy<RequestNewBankAccountCommand>,
  IHandleMessages<BankAccountApprovedEvent>
{
  ...
}

這兩個介面組成單一的控制代碼方法,如下所示︰

public void Handle(RequestNewBankAccountCommand message) { ... }
public void Handle(BankAccountApprovedEvent message) { ... }

讓我們切換回到您假設在 UI 中的 HTML 表單。當銀行員工按一下送出新的銀行帳戶的客戶的要求時,命令會推送到匯流排和匯流排會以無訊息方式觸發新的傳奇。最後,執行指定命令的傳奇的控制代碼方法。

將行為加入至傳奇

傳奇類別具現化,如下所示︰

public AccountRequestSaga(
  IBus bus, IEventStore eventStore, IRepository repository)
  : base(bus, eventStore, repository)
{
}

它會取得匯流排的參考,讓目前的傳奇可以其他巨作或處理常式和處理 denormalizers 匯流排推送新的命令和事件。這是實際的啟用彈性且敏捷的商務工作流程設計的關鍵因素。此外,傳奇取得存放庫的參考。MementoFX,在儲存機制是建置於事件存放區的外觀。儲存機制儲存,並傳回彙總,只不過每次重新執行的所有事件進入重建彙總狀態。很棒 MementoFX 存放庫也會提供多載,以便在某個日期查詢指定的彙總的狀態。

以下是會保存新銀行帳戶要求傳奇︰

public void Handle(RequestNewBankAccountCommand message)
{
  var request = AccountRequest.Factory.NewRequestFrom(
    message.FullName, message.Age, message.IsNew);
  Repository.Save(request);
}

在此範例中,AccountRequest 類別是 MementoFX 彙總。MementoFX 彙總都是衍生自特定父系的一般類別。指定父類別可省下您省卻編碼一堆東西,就有關管理內部網域事件︰

public class AccountRequest : Aggregate,
  IApplyEvent<AccountRequestReceivedEvent> { ... }

另一個有趣的層面 MementoFX 彙總為 IApplyEvent 介面。IApplyEvent 介面相關聯的型別定義網域事件的相關追蹤彙總。換句話說,它表示 IApplyEvent 介面相關聯的所有事件均會都儲存在事件彙總類別的執行個體存放區。因此,如果銀行帳戶要求,就可以知道何時收到,其處理時間、 核准,延遲,拒絕等等。此外,所有的事件會儲存在它們的自然順序,這表示,您可以輕易的架構,以便採取的特定日期為止的所有事件,並且在任何時間系統的生命週期中傳回的彙總檢視。請注意,在 MementoFX IApplyEvent 使用選擇性的也很也歡迎手動叫用的彙總的其他方法時,保存存放區的相關事件。介面的使用是建議的作法使更清楚且簡潔的程式碼。

當定義彙總,您必須指出其唯一識別碼。依照慣例,MementoFX 會辨識為識別碼的彙總的類別,再加上 「 識別碼 」 名稱的屬性 在此情況下,它會一直 AccountRequestId。如果您想要使用其他名稱 (例如 RequestId),您會使用 AggregateId 屬性,如下所示︰

public void ApplyEvent(
  [AggregateId("RequestId")]
  AccountRequestReceivedEvent theEvent)
{ ... }

在 C# 6 中,您也可以使用 nameof 運算子,以避免在已編譯的程式碼中使用一般的常數。MementoFX 和 ECS 方法,您需要修改一些您可能會用來持續性邏輯。例如,傳奇来記錄的帳戶要求時,它會使用 AccountRequest 的處理站以取得新的執行個體。請注意,為了避免編譯階段錯誤,必須 AccountRequest 類別主體中定義的 factory 類別︰

public static class Factory
{
  public static AccountRequest NewRequestFrom(string name, int age, bool isNew)
  {
    var received = new AccountRequestReceivedEvent(Guid.NewGuid(), name, age, isNew);
    var request = new AccountRequest();
    request.RaiseEvent(received);
    return request;
  }
}

如您所見,處理站不填寫新建立的執行個體的彙總,只是準備事件,並引發。RaiseEvent 方法屬於彙總的基底類別的作用是將該事件加入至目前的執行個體的彙總,呼叫 ApplyEvent。因此,顯然複雜的方式,您可以在超出 factory 傳回完整初始化彙總。好處,不過是不只是彙總持有的目前狀態,但它也包含所有目前的操作有引進的相關事件。

傳奇將彙總儲存至持續性層級時,會發生什麼事? 內建的儲存機制的 Save 方法在彙總經歷的擱置事件清單,並將它們下設定的事件存放區。改為呼叫 GetById 方法時,它會擷取所有相關的事件識別碼,並傳回所產生的所有記錄的事件重新執行彙總的執行個體。[圖 3 顯示 UI,幾乎是猜出標準的方法。不過,實際發生什麼是相當不同。請注意,在 UI 中我使用 ASP.NET SignalR 將變更回主頁面。

範例 MementoFX 應用程式執行動作
[圖 3 範例 MementoFX 應用程式執行動作

Denormalizers 上文字

其中一個最重要的變更,在軟體中最近是適合用來儲存資料的模型與理想取用資料,根據 CQRS 模式的模型之間的分隔。到目前為止,您剛才儲存的彙總所有與儲存相關的資訊。每一種使用者,不過,可能有一組不同的相同的彙總的相關資訊。當發生這種情況時,您需要建立一或多個儲存的資料投射。在此內容中的投射是幾乎相同的 SQL Server 資料表中的檢視。您可以使用 denormalizers 來建立彙總的投影。Denormalizer 是繫結至匯流排推入的事件處理常式。例如,假設您需要建立負責核准新的帳戶要求管理員儀表板。您可能想要提供稍有不同的彙總,可能是僅有一些與企業相關的指標相同的資料︰

public class AccountRequestDenormalizer :
  IHandleMessages<AccountRequestReceivedEvent>
  {
    public void Handle(AccountRequestReceivedEvent message)
    { ... }
}

正規化的資料並不需要事件存放區中。於合理範圍內,您可以使用您想要的任何資料庫,且大部分的情況傳統的關聯式引擎最有效的解決方案。

總結

此資料行提供一窺新的方式來組織商務邏輯將放在一起應用 CQRS 與事件來源,但是沒有處理低階細節和複雜的兩種模式。此外,ECS 方法也是誤解的接近實際的商業偏好的通訊,並降低風險。MementoFX 是供您試用 nuget。我無法等聽聽您的意見。


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