2016 年 9 月

第 31 卷,第 9 期

本文章是由機器翻譯。

技術最前線 - 現實中以訊息為基礎的商務邏輯

Dino Esposito |2015 年 9 月

Dino Esposito是,若要建立有效率的商業軟體,您需要仔細模型解決商務事件和準確地反映商務程序的一般知識。

20 幾年前,在流程圖中用來代表大量的應用程式區塊的理想工具。多年來,流程圖的大小已成長以致變得不切實際的控制代碼和工程師開始尋找其他位置尋找適當的模型,簡述商務的複雜性。完整的全部包含統一模組化語言 (UML) 圖表似乎是最好的選擇,從類別圖表中,用來傳達網域模型開始。似乎是 Domain-Driven 設計 (DDD) 和它的本質的許多專案中,但是也失敗,原因包括設計不良的技能,缺乏足夠的多種技術的通訊,而且不足,無法了解公司的機制,處理。

軟體工程新推出與更多現代方法是機制的查看商務事件,並使用這類事件收集需求並建置更深入瞭解系統。換句話說,而不是可用手動方式建立新的模型,觀察到的企業,您的目標是透過程式碼鏡像相同商務程序說明專案關係人和分析師,如此一來,因此使用哪一個使用者的程序是很熟悉。

在本專欄中,我會提供實用的示範,這種方法所表示相關的商務程序,透過軟體實體稱為 「 巨作 」。 此外,我將使用臨機操作的檢視模型來填入每個與使用者熟悉的畫面。最後,我將分割的每個應用程式工作流程的訊息 (命令和事件) 的組合所涉及的合作對象所交換的步驟。您會發現被得表示即使是複雜部分商務邏輯的方式更容易和以上的任何其他、 如何快速且可靠它可以變更 「 保持與變數的商務規則的同步處理的實作。

我將介紹一大堆新的軟體實體,例如巨作、 命令、 事件、 匯流排和應用程式服務。不造任何新的應用程式,並儲存大量的重複程式碼 — 我也會描繪出架構,可協助您實作此類型的事件為基礎的架構的特性。

達成相同的替代方式

放在一起,「 鏡像的商務程序 」 和 「 鏡像使用者的程序 」 彌補相當不同的方式設計軟體的策略。由上而下,而不是由下往上,則會成為整體方法。一個可行的方法可以開始設計從展示層,這主要做面對使用者的螢幕擷取畫面和精靈背後的檢視模型所驅動系統。發生,請使用者輸入資訊的畫面中,這會形成的螢幕檢視模型,而且會變成對系統進行任何後續的商務動作的輸入。

當使用者使用 UI,命令會推入至應用程式層,最上層的基底負責擷取任何使用者輸入,並傳回任何的商務邏輯所需的檢視。應用程式層級被視為應用程式的使用案例背後的邏輯協調。一般而言,應用程式層被實作為一組服務,以便填入都是一對一與使用者動作的方法的類別程式庫肯定可以達成此目的。在 ASP.NET MVC 應用程式內容中,應用程式層的類別可以是一對一的控制器類別和商務作業就會觸發每個控制器方法最後會呼叫特定應用程式層方法以一對一的方式中所示 [圖 1

檢視模型和應用程式層
[圖 1] 檢視模型和應用程式層

接下來,我將說明如何重新寫入的一般商務動作,例如註冊發票使用以訊息為基礎的方法。

呈現方式和上一步]

做為參考,我將使用 Andrea Saltarello 和我最初我們 Microsoft Press 架構活頁簿中,撰寫為隨附程式碼的範例應用程式的摘錄,」 Microsoft.NET — Architecting 的企業應用程式,第 2 版 」 (2014)。在過去一年,大幅演變的程式碼,就可以找到它在 bit.ly/29Nf2aX MERP 資料夾下。

MERP 是在兩個區域中的 ASP.NET MVC 應用程式。每個區域接近所繫結的內容中的 DDD 行話,而且每一個可以視為,更廣泛來說,微服務。ASP.NET MVC 中一個區域是不同的控制器、 模型和 Razor 檢視集合。說,接著,您必須具有問題方法 InvoiceController 類別。實際上,問題的動作透過實作不同的 GET 和 POST 方法中所示 [圖 2

[圖 2 GET 和 POST 方法的控制器動作

[HttpGet]
public ActionResult Issue()
{
  var model = WorkerServices.GetIssueViewModel();
  return View(model);
}
[HttpPost]
public ActionResult Issue(IssueViewModel model)
{
  if (!this.ModelState.IsValid)
  {
    return View(model);
  }
  WorkerServices.Issue(model);
  return Redirect("/Accountancy/");
}

該成員 WorkerServices 是應用程式層中的進入點,而且已透過相依性插入控制器類別中插入的參考︰

public InvoiceController(InvoiceControllerWorkerServices workerServices)
{
  if(workerServices == null)
    throw new ArgumentNullException(nameof(workerServices));
  WorkerServices = workerServices;
}

GET 方法的結構是相當簡單︰ 應用程式層擷取檢視模型,其中包含向使用者畫面觸發 「 問題 」 動作所需的所有資料。Razor 檢視是被當做背景工作服務傳回的資料。

POST 方法會依照 CQRS 邏輯,並執行該命令將發行的發票,因此改變系統的狀態。命令不一定會產生任何直接回饋的使用者,但從實際會更新 UI 的任何後續查詢分開命令所發生的重新導向。

將所需的邏輯

在傳統的實作案例中,背景工作服務方法會收集輸入的資料,並觸發硬式編碼,循序工作流程。在工作流程結束時,服務方法會挑選結果,並將它們封裝到上層的程式碼傳回的資料結構。

工作流程通常是由條件分支、 迴圈及任何可以用來表示所需的邏輯的整合程序。工作流程可能受到啟發由網域專家所畫的流程圖,但在一天結束其實是在其中的工作流程透過建構和複雜的程式設計語言或臨機操作的工作流程架構扁平化軟體成品。進行更小的變更可能會顯著的迴歸效果。雖然單元測試來控制迴歸,它們是仍然通常視為額外的負擔,需要額外的成本和人力。

讓我們看看結果如何,相反地,如果協調的商務程序,使用命令。

發送到匯流排的命令

訊息為基礎的軟體而言,命令會為資料,也就是 POCO 類別只有屬性和方法的封包。以抽象的用語,相反地,命令可以描述為命令式訊息傳送至系統必須執行一些工作。以下是如何按下命令,系統可以觸發一項工作︰

public void Issue(IssueViewModel model)
{
  var command = new IssueInvoiceCommand(
    model.Date,
    model.Amount,
    ...
  );
  Bus.Send(command);
}

檢視模型包含系統處理所有的輸入的資料。ASP.NET mvc 模型繫結層已經會張貼的資料和命令資料之間的適當對應工作。如果您不想要做為目標的 ASP.NET MVC 模型繫結使用的命令類別,它會出現不為過。實際上,,模型繫結都是在控制器內容是展示層的一部分而命令則會遵循應用程式層級或甚至之後的任何決策的訊息。無可否認地,在上述程式碼片段中,正在檢視模型類別,一份即將命令類別,但這通常是由於造成極簡單的範例。

準備的命令執行個體之後下, 一個步驟將它傳遞給系統。在傳統的軟體設計中,命令具有會負責處理它從開始到結束執行程式。此處的差別是,系統會拒絕任何命令,而且更重要的是,命令的效果,以及實際的處理常式的清單可能系統的狀態而有所不同。

匯流排是主要負責尋找命令的處理常式的發行/訂閱機制。處理常式的效果可以是單一不可部分完成的動作或命令可以觸發傳奇的更有可能的原因。傳奇是通常包含多個命令和其他巨作和處理常式的事件,以回應的通知的已知的商務程序的執行個體。 

匯流排的訊息基礎架構的中心元素。起碼,匯流排介面是有可能包括下列方法︰

public interface IBus
{
  void Send<T>(T command) where T : Command;
  void RegisterSaga<T>() where T : Saga;
  void RegisterHandler<T>();
}

除了用來發行命令之傳送方法匯流排通常會提供註冊巨作和處理常式的方法。巨作和處理常式是類似的軟體元件,也就是這兩個處理命令。處理常式,不過,會啟動,以及在單一執行完成而不需要回匯流排。一種常見的處理常式是 denormalizer,也就是完整的 CQRS 結構中儲存的彙總的目前狀態的唯讀投影的元件。傳奇可以由多個命令和推至其他處理常式和巨作匯流排回應的事件通知的多步驟程序。

設定 SUP

組態匯流排的應用程式啟動期間,會發生。此時,在巨作,並選擇性地處理常式中加以分割商務程序。每個傳奇完全識別起始訊息 (命令或事件),它會處理的訊息的清單。[圖 3 舉例說明。

[圖 3 設定匯流排

// Sagas
var bus = new SomeBus();
...
  bus.RegisterSaga<IncomingInvoiceSaga>();
  bus.RegisterSaga<OutgoingInvoiceSaga>();
// Handlers (denormalizers)
bus.RegisterHandler<IncomingInvoiceDenormalizer>();
bus.RegisterHandler<InvoiceDenormalizer>();
bus.RegisterHandler<OutgoingInvoiceDenormalizer>();
// Here’s a skeleton for the saga components above.
public class IncomingInvoiceSaga : Saga,
  IAmStartedBy<RegisterIncomingInvoiceCommand>
{...
}
public class OutgoingInvoiceSaga : Saga,
  IAmStartedBy<IssueInvoiceCommand>
{
  ...
}

傳奇類別通常繼承自基底類別,輕鬆地封裝的資產,您可能要使用的事件存放區、 匯流排等彙總的儲存機制的參考。如前所述,傳奇亦可依入門訊息,其中 T 是事件或命令的 < T > IAmStartedBy 介面的摘要︰

public interface IAmStartedBy<T>
{
  void Handle(T message);
}

如您所見,IAmStartedBy < T > 介面計算單一方法,處理,此 cmdlet 會取得類型 t 的執行個體控制代碼的任何方法的主體包含實際的程式碼,註冊發票等等,一般而言,執行商務工作。在工作結束時,可能需要通知其他巨作或處理常式發生了什麼事。特別是,您可能要引發的事件,讓其他人知道發票已成功註冊,或是否失敗,原因為何。「 已失敗 」 通知的處理常式會負責將任何補償或復原程序,並且產生任何類型的回應給使用者。

從巨作的流程圖

流程圖網域專家可能可以使用大綱商務程序和如何在程式碼中定義巨作之間沒有固有的相似度。方式,傳奇是流程圖實作其中每個區塊可能會收到之事件或命令,以處理透過轉譯。傳奇可以是長時間執行的程序和甚至暫停並繼續。想像成,例如,商務程序核准步驟組成 (例如進行離線的研究的客戶之後)。必須啟動傳奇和核准階段到達時應該會收到事件,並取得序列化的處理常式。接下來,傳奇將繼續執行一些程式碼傳送 witnesses 核准的命令時。如您所見,有分割微步驟中的商務邏輯也容易擴充,並變更的邏輯與不斷演變的企業都保持同步。

從架構的理論

到目前為止,我假設巨作等匯流排元件存在。誰是寫入匯流排,以及處理巨作的持續性? 和介面和基底定義的類別,我提到 framework 的一部分嗎?

如果您查看原始程式碼的專案從 MERP bit.ly/29Nf2aX, ,您會看到它使用類似匯流排、 傳奇、 命令及更多的類別。在程式碼的最新版本,不過,這些類別來自幾個新的 NuGet 封裝,統稱為 MementoFX。一般名冊架構的應用程式需要定義基底類別和匯流排 (Memento.Messaging.Postie 或目前 Memento.Messaging.Rebus) 和事件的持續性的協助程式封裝的兩個核心 MementoFX 封裝。目前,MERP 原始程式碼使用持續性的內嵌 RavenDB 引擎,並將它包裝在 Memento.Persistence.EmbeddedRavenDB 套件︰ 如此一來,將啟動事件存放區,因為 ASP.NET 處理序已啟動。

藉由使用 MementoFX 封裝,您可以大幅減少建置訊息為基礎的商務邏輯的工作。讓我聽到您的想法 !


Dino Esposito的共同著作"Microsoft.NET: 架構的企業應用程式 」 (Microsoft Press,2014年) 和 [使用 ASP.NET 最新 Web 應用程式] (Microsoft Press,2016年)。Microsoft.NET Framework 與在 JetBrains 的 Android 平台和經常在世界各地的產業活動演講的技術推廣人員 Esposito 分享他在軟體的願景 software2cents.wordpress.com 和在 Twitter 上: @despos

感謝以下的微軟技術專家對本文的審閱: 和 Andrea Saltarello