2016 年六月

第 31 卷,第 6 期

本文章是由機器翻譯。

技術最前線 - 建置歷程 CRUD,第 2 部

Dino Esposito | 2016 年 6 月

Dino Esposito在概念上來說,歷程記錄的建立、 讀取、 更新、 刪除 (CRUD) 是的傳統 CRUD 擴充的額外參數 ︰ 日期。歷程記錄的 CRUD 可讓您新增、 更新和刪除資料庫的記錄,並讓您查詢特定時點的資料庫狀態的時間。歷程記錄的 CRUD 可讓您的應用程式內建的基礎結構商業智慧分析和進階報表功能。

在上個月的專欄 (msdn.com/magazine/mt703431),我介紹了歷史的 CRUD 系統的理論基礎。在本文中我會提供實用的示範。

提供範例案例

基於本文的目的,我會視為簡單預約系統。比方說,讓的系統一家公司在內部使用,讓員工書籍會議室。最後,此類軟體是一般的 CRUD 保留位置建立新記錄的位置。如果會議會移至不同的時間更新或刪除已取消會議相同記錄。

如果您撰寫程式碼做為一般的 CRUD 這類的預約系統,您知道系統的最新狀態,但是會遺失更新和刪除會議有關的資訊。這真的是問題嗎? 其實全得視情況而定。如果只要查看會議上實際業務的影響,它並可能不是問題。不過,如果您想要尋求改善員工的整體效能的方法,然後歷史 CRUD 追蹤更新和刪除的記錄,可能會顯露太多次會議已移動,或已取消,可能是較差的內部程序或不正確的態度的正負號。

[圖 1 呈現實際的 UI,空間預約系統。基礎資料庫是幾個連結資料表的 SQL Server 資料庫 ︰ 聊天室和預約。

預約系統的前端 UI
[圖 1 預約系統的前端 UI

範例應用程式被設計為 ASP.NET MVC 應用程式。當使用者按一下提出要求時,控制器方法會介入,並處理張貼的資訊。下列程式碼片段提供充份的處理要求的伺服器端的程式碼 ︰

[HttpPost]
public ActionResult Add(RoomRequest room)
{
  service.AddBooking(room); 
  return RedirectToAction("index", "home");
}

方法屬於 BookingController 類別,並委派至服務類別時插入背景工作的組織的實際工作負擔。方法的實作的有趣層面是,它將重新導向至首頁中 [圖 1 之後建立預約。沒有明確檢視所建立時新增登記作業的結果。這是選擇命令查詢責任隔離 (CQRS) 架構的副作用。新增預約命令張貼至後端。它會改變系統的狀態,這樣就大功告成了。範例應用程式使用 AJAX 回傳、 有本人不需要更新任何項目和命令本人獨立作業,且 UI 不可見的連結。

傳統的 CRUD 和歷程記錄的 CRUD 核心差異是,後者會持續追蹤的變更系統的狀態開始之後的所有作業。若要規劃歷程記錄的 CRUD,應該視為的企業營運您找出這些命令提供系統以及機制的命令。每個命令會改變系統狀態和歷程記錄的 CRUD 會持續追蹤的每個系統的狀態。任何到達的狀態會記錄為事件。事件是只與不可變的描述項目已發生。事件清單之後,您可以建立多個預測的資料,最常用的是只涉及的商業實體的目前狀態。

應用程式中,事件是直接從使用者命令的執行,或間接從其他的命令或外部輸入。在此範例案例中,您預期使用者按一下按鈕以 post 預約要求。

處理命令

以下是控制器的可能的應用程式的 AddBooking 方法實作 ︰

public void AddBooking(RoomRequest request)
{
  var command = new RequestBookingCommand(request);
  var saga = new BookingSaga();
  var response = saga.AddBooking(command);
  // Do something based on the outcome of the command
}

RoomRequest 類別是由 ASP.NET MVC 繫結層,張貼的資料填入的純文字的資料傳輸物件。RequestBookingCommand 類別,而是儲存執行命令所需的輸入的參數。在這類簡單案例中,兩個類別幾乎同時發生。如何處理命令? [圖 2 的處理命令的索引鍵三個步驟。

核心步驟來處理命令鏈結
[圖 2] 的核心步驟來處理命令鏈結

此處理常式是元件會接收命令,並加以處理。可以透過直接在記憶體中呼叫,從背景工作的服務程式碼中叫用處理常式,或您可以在匯流排中間,如下所示 ︰

public void AddBooking(RoomRequest request)
{
  var command = new RequestBookingCommand(request);
  // Place the command on the bus for
  // registered components to pick it up
  BookingApplication.Bus.Send(command);
}

匯流排可能將幾個好處。其中一個是您可以輕鬆地處理的案例中的多個處理常式可能都需要存取相同的命令。另一個好處是匯流排可能會設定為是可靠的傳訊工具,以確保訊息的傳遞一段時間和克服可能的連線問題。此外,在匯流排只是讓您能夠記錄命令的元件。

此處理常式可能會啟動,並結束於同一個要求中的簡單一次性元件或長時間執行工作流程會採用小時或天才能完成,並可能等候某一點人力認可暫止。不是簡單的一次性工作,通常會執行程式的處理常式呼叫巨作。

一般情況下,您使用的匯流排或佇列,如果您有特定的延展性和可靠性方面的需求上。如果您只想建置代替傳統 CRUD 歷史 CRUD,您可能不需要使用匯流排。無論您使用的匯流排或不是,在某個時間點命令可能會到達其一次性或長時間執行的處理常式。應該執行任何作業,都必須處理常式。大部分的工作所組成的資料庫上的核心作業。

記錄命令

在傳統的 CRUD,將資訊寫入至資料庫就表示在新增記錄配置傳入的值。不過,在歷程記錄的 CRUD 觀點來看,新增的記錄代表預約建立的事件。建立的事件的預約是特定的資訊的獨立且不可變,包括事件、 時間戳記、 名稱和引數之事件清單的唯一識別碼。建立事件的引數通常會包含新加入的預約記錄傳統預約資料表中會填入的所有資料行。相反地,更新引數是事件的只能實際更新的欄位。接著,所有的更新的事件可能沒有相同的內容。最後,已刪除的事件引數的限制為唯一識別預約的值。

任何的歷程記錄的 CRUD 作業所組成的兩個步驟 ︰

  1. 記錄事件和其相關的資料。
  2. 請確定目前的系統狀態會立即且快速地查詢。

如此一來,系統的目前狀態永遠可用且最新狀態而且它會造成的所有作業也可用於任何進一步的分析。請注意,「 目前系統狀態 」 只是您在傳統的 CRUD 系統中看到的唯一狀態。簡單的 CRUD 系統內容中有效記錄的事件,並更新系統狀態的步驟就會以同步方式以及在相同交易內所示 [圖 3

[圖 3 個記錄的事件和更新系統

using (var tx = new TransactionScope())
{
  // Create the "regular" booking in the Bookings table   
  var booking = _bookingRepository.AddBooking(
    command.RoomId, ...);
  if (booking == null)
  {
    tx.Dispose();   
    return CommandResponse.Fail;
  }
  // Track that a booking was created
  var eventToLog = command.ToEvent(booking.Id);
    eventRepository.Store(eventToLog);
  tx.Complete();
  return CommandResponse.Ok;
}

情況是,每當您加入、 編輯或刪除預約記錄您維持整體的預約清單保持最新狀態時的了解的準確事件順序的目前狀態所造成的例外狀況。[圖 4 顯示兩個 SQL Server 資料表涉及在範例案例與它們的內容之後插入和更新。

預約和 LoggedEvents 資料表並排顯示
[圖 4 預約和 LoggedEvents 資料表並排顯示

預約表列出在系統中與每個找到的所有相異預約傳回目前的狀態。LoggedEvents 資料表列出各種預約它們所錄製的順序的所有事件。例如,預約 54 已經建立在指定的日期,並修改幾天後。在範例中,在圖片中的貨物資料行儲存正在執行的命令的 JSON 序列化資料流。

在 UI 中使用記錄的事件

讓我們假設已授權的使用者想要查看的暫止預約詳細資料。可能使用者會取得預約從行事曆清單,或透過以時間為基礎的查詢。在這兩種情況下,預約基本事實 —、 時間和人員 — 都已經已知和詳細資料,甚至是檢視可能會不太有用。真的可能很有幫助,不過,如果中所示,您可以顯示的預約,整個歷程記錄 [圖 5

使用 UI 中的記錄的事件
[圖 5 耗用記錄在 UI 中的事件

閱讀所記錄的事件,您可以建立檢視模型,其中包含一份相同的彙總實體的狀態 — 預約 #54。在範例應用程式中,當使用者按一下 [檢視預約詳細資料強制回應快顯視窗隨即出現,並在背景中下載一些 JSON。傳回 JSON 的端點如下所示 ︰

public JsonResult BookingHistory(int id)
{
  var history = _service.History(id);
  var dto = history.ToJavaScriptSlotHistory();
  return Json(dto, JsonRequestBehavior.AllowGet);
}

Worker 服務上的記錄方法會執行大部分的的工作。這項工作的核心部分查詢與指定的登記識別碼相關的所有事件 ︰

var events = new EventRepository().All(aggregateId);
foreach (var e in events)
{
  var slot = new SlotInfo();
  switch (e.Action)
  {
    :
  }
  history.Changelist.Add(slot);
}

循環記錄的事件,適當的物件會附加至要傳回的資料傳輸物件。某些 ToJavaScriptSlotHistory 中執行的轉換可快速並輕鬆地顯示在表單中的兩個連續狀態之間的差異] 所示 [圖 5

它是值得注意,不過,雖然記錄事件,甚至在 CRUD 允許這類好用的增強功能,在 UI 中,最大值在於現在知道曾經發生在系統中的所有事情,可以處理該資料來擷取的資料,您可能需要在某個時間點的任何自訂投影。例如,您可能建立更新的統計資料,並讓分析師來要求會議室的整個程序無法在公司因為人們往往書籍的結論,然後更新或刪除。您也可以追蹤這種情況是預約的直接查詢事件記錄在那之前及計算後續的項目狀態的特定日期。簡單的說,歷程記錄的 CRUD 開啟了全新紀元的應用程式的可能性。

總結

歷程記錄的 CRUD 只是更聰明的方法發展純 CRUD 應用程式。在本文中,觸及流行語和有更多潛在的例如 CQRS、 事件來源、 匯流排和佇列,並以訊息為基礎的商務邏輯的模式。如果您找到這篇文章很有幫助,我建議您回頭閱讀我的 2015 年 (msdn.com/magazine/mt238399) 與 2015 年 8 月 (msdn.com/magazine/mt185569) 資料行。根據此範例中,您可能會發現這些文件更熱愛 !


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

感謝以下的微軟技術專家對本文的審閱: 喬恩 · Arne Saeteras