本文檔詳細介紹了 foodtruacker,這是一個實施領域驅動設計 (DDD)、CQRS 和事件溯源的專案。它使用 ASP.NET Core,專注於提高複雜業務領域的可維護性。該項目使用一個簡化的虛構業務案例來進行說明。這個詳細的解釋涵蓋了動機、特徵、實現細節和相關技術。
foodtruacker - DDD、CQRS 和事件溯源的實施
這個事件驅動的專案利用了原則、框架和架構——所有這些都圍繞著在處理反映複雜業務領域的系統時增強可維護性的想法。該應用程式的 Web API 是基於 Microsoft 的 ASP.NET Core 框架構建,並實現域驅動設計以及 CQRS 和事件來源模式。一個虛構的商業案例奠定了該項目的基礎,並且是事件風暴研討會的結果。
請注意:該專案引入的虛構業務領域已被大大簡化,只能被視為相關用例的提供者。
動機
由於在業務領域相當複雜的專案中使用 CRUD 操作和 POCO 物件並不總是最好的,因此我決定創建這個專案作為我對領域驅動設計 (DDD) 的研究和興趣的實際實現開發軟體的方法。
由於該專案引入的虛構業務案例很大程度上是事件驅動的,因此我決定也實現 CQRS 和事件溯源模式。在為這個專案進行研究時,兩者都引起了我的注意,並且與 DDD 配合得很好。
特徵
概述
該專案由一個可執行的 Web API 應用程式和幾個功能元件組成,每個元件都透過類別庫提供。代碼按名稱空間組織。在 Visual Studio 中建立的 ASP.NET Core 應用程式中,預設情況下,命名空間是從專案的資料夾結構自動建立的。請參閱下圖以了解該專案的資料夾(和命名空間)結構的概覽:
介紹
事件風暴
由 Alberto Brandolini 發明的一種靈活的研討會形式,用於協作探索複雜的業務領域。它是一種極其輕量級的方法,可用於快速改進、設想、探索和設計組織內的業務流程和流程。
研討會由一群具有不同專業知識的人員組成,他們使用彩色便利貼協作來佈置相關業務流程。 EventStorming 研討會必須有合適的人員在場,並且有足夠的表面積來放置便條。所需人員通常包括知道要問的問題的人(通常是開發人員)和知道答案的人(領域專家、產品所有者)。
本次研討會的目的是讓參與者互相學習、揭示和反駁誤解,並為開發反映相關業務領域的基於事件的軟體解決方案奠定基礎(例如在這個 GitHub 專案中)。
領域驅動設計(DDD)
一種軟體開發方法,其開發集中於對域模型進行編程,該域模型對相關業務域的流程和規則有豐富的了解。 「領域驅動設計」一詞是由 Eric Evans 在他的同名書中創造的。
DDD 旨在簡化複雜應用程式的創建,並專注於三個核心原則:
Eric Evans 的書定義了一些領域驅動設計的常用術語:
領域模型
描述業務域的流程和策略並用於處理與該網域關聯的所需任務的抽象系統。
無所不在的語言
業務領域某些元素的字詞和陳述。為了再次避免誤解,所有團隊成員都應採用某些術語,通常是領域專家使用的術語。
有界上下文
定義和應用特定領域模型的概念邊界。這通常代表一個子系統或一個工作領域。它主要是一種語言界定,每個有界上下文都有自己的通用語言。
例如:客戶管理,其中使用者稱為“客戶”。
Eric Evans 的書進一步區分了領域模型的某些部分。僅舉幾例:
實體
由其身分而不是其屬性定義的物件。
例如:一個人永遠是同一個人,無論在某個時刻選擇什麼夾克、頭髮顏色或說什麼語言。
值對象
僅由其屬性值定義的物件。值物件是不可變的,並且沒有唯一的識別。值物件可以被具有相同屬性的其他值物件替換。
例如:當聚焦於一個人時,一副破損的太陽眼鏡可以輕鬆地用一副外觀相同的新太陽眼鏡替換。
總計的
一個或多個實體和可選值物件的集群,統一為單一事務單元。一個實體將構成聚合的基礎,因此被宣告為聚合根。它的所有協作實體和值物件的屬性只能透過這個單一的基礎實體來存取。聚合必須始終處於一致狀態。在物件導向程式設計中,這通常是透過使用私有 setter 和受保護 getter 來完成的。
例如:在汽車銷售環境中,一輛汽車(實體)由其車輛識別號碼定義。這輛車可能有四個輪子(值物件),在一定時間後可能需要更換。
領域事件
作為域模型中活動的結果而建立的物件。它用於保存和轉發與此活動相關的資訊。領域事件通常是為領域專家認為相關的那些活動而創建的。
六角形架構(連接埠和適配器)
一種用於軟體設計的架構模式,由 Alistair Cockburn 於 2005 年提出。每層使用介面(連接埠)和實作(適配器)與相鄰層通訊:
這種架構模式的關鍵規則是依賴關係只能指向內部。內圈中的任何事物都無法了解外圈中的任何事物。任何願意向外指向的依賴項,例如從應用程式層呼叫資料庫,都需要透過控制反轉 (IoC) 或依賴項注入 (DI) 進行實例化。
使用 MediatR(預先建置的訊息傳遞框架)的 CQRS
CQRS 代表命令/查詢責任分離,由 Greg Young 於 2010 年首次提出。 CQS 規定:
CQRS 相對於 CQS 的改進在於,這些命令和查詢被視為模型而不是方法。這些模型可以在某一點作為物件進行分派,然後由系統中另一點所需的相應處理程序進行處理,每個處理程序返回其回應模型,以明確隔離每個操作。
中介模式允許利用中介物件來實現鬆散耦合的命令/查詢和處理程序。對象之間不再直接通信,而是透過中介者進行通信。
MediatR 框架是中介者模式的開源實現,由 Jimmy Bogard 創建。它將在本專案中用於框架層和應用程式層之間的通訊。它還將用於將資料從命令資料庫投影到查詢資料庫。
事件溯源
一種架構設計模式,用於儲存應用程式狀態的每個更改,而不是僅儲存域中資料的當前狀態。這種模式由 Greg Young 提出,此後被廣泛採用。
此模式旨在將應用程式狀態的每次變更擷取為事件物件。然後,這些事件物件會按照發生的順序以僅附加的方式儲存。這不僅允許在迄今為止發生的事件序列上重新創建物件的當前狀態,而且最終允許及時返回並重新創建任何給定時間的物件狀態。
銀行帳戶是事件溯源原則的一個很好的例子。每次提款或存入資金時,不僅會更新當前餘額,還會記錄變動金額。然後透過檢查事件序列以及每次提取或存入金額的相應資訊來計算當前餘額。
事件溯源與領域驅動設計配合得很好,因為它非常適合儲存領域事件,由領域模型在每次更改請求時觸發。
事件溯源也從 CQRS 中受益匪淺。不必對事件溯源資料庫進行查詢,事件溯源資料庫必須遍歷與所要求的物件相關的所有記錄事件才能重新建立目前狀態,而是可以針對專用查詢資料庫進行此查詢。此查詢資料庫由自己的事件處理程序更新,偵聽附加到事件來源資料庫後立即調度的相同事件。這些更新過程稱為預測。
這種資料庫的分離也為可擴展性和效能優化的巨大潛力奠定了基礎。只需讓其事件處理程序偵聽在應用程式狀態發生相關變更後立即從事件來源資料庫用戶端分派的事件,即可建立查詢資料庫的多個實例並保持同步。資料庫類型的選擇以及資料非規範化程度、每個查詢的最佳化可以大大提高效能。
讀取模型的這種不斷更新可以同步或非同步發生。後者是以最終一致性為代價的,讀取模型與寫入模型在很小的時間間隔(通常是毫秒)內不同步。
共享核心
領域層的通用庫,其中包含通用的領域驅動設計特定基類、領域實體、值物件等,這些在有界上下文之間共用。
入門
若要按原樣啟動並運行該項目,請隨意執行以下步驟:
先決條件
設定
在瀏覽器中啟動 https://localhost:5001/swagger/index.html 以查看 API 的 Swagger 文件。
使用 Swagger、Postman 或任何其他應用程式將 POST 要求傳送至 https://localhost:5001/api/Administration/Register 以註冊您的初始管理員帳戶。發送以下對象:
查看控制台應用程式或為應用程式日誌重新配置的輸出。用戶成功註冊後,應該會有一個電子郵件驗證連結(由 EmailService 提供)寫入日誌中。將此網址複製並貼上到瀏覽器中,然後按 Enter 鍵完成註冊。請隨意更改或基於此不正確的電子郵件服務實施;-)
你已經準備好了。接下來登入。
在瀏覽器中啟動 http://localhost:2113/ 以查看 EventStoreDB GUI。開啟「串流瀏覽器」標籤以查看所有儲存的事件。
可以透過執行以下命令來執行測試:
科技
此專案使用以下技術/NuGet 套件:
資源/推薦閱讀
阿爾貝托·布蘭多里尼:
https://www.eventstorming.com
沃恩‧弗農:
https://dddcommunity.org/wp-content/uploads/files/pdfarticles/Vernon2011_1.pdf
https://dddcommunity.org/wp-content/uploads/files/pdfarticles/Vernon2011_2.pdf
https://dddcommunity.org/wp-content/uploads/files/pdfarticles/Vernon2011_3.pdf
阿利斯泰爾·科伯恩:
https://web.archive.org/web/20180822100852/http://alistair.cockburn.us/Hexagonal+architecture
羅伯特·C·馬丁(鮑伯叔叔):
https://blog.cleancoder.com/uncle-bob/2012/08/13/the-clean-architecture.html
塞薩爾·德拉托雷、比爾·瓦格納、麥克·魯索斯:
https://docs.microsoft.com/en-us/dotnet/architecture/microservices/
格雷格·楊
https://cqrs.files.wordpress.com/2010/11/cqrs_documents.pdf
https://cqrs.wordpress.com/documents/building-event-storage/
https://msdn.microsoft.com/en-us/library/jj591559.aspx
馬丁·福勒:
https://www.martinfowler.com/bliki/CQRS.html
吉米·博加德:
https://github.com/jbogard/MediatR
https://www.youtube.com/watch?v=SUiWfhAhgQw
領域驅動設計:
https://dddcommunity.org
https://thedomaindrivendesign.io
https://dotnetcodr.com/2013/09/12/a-model-net-web-service-based-on-domain-driven-design-part-1-introduction/
https://dotnetcodr.com/2015/10/22/domain-driven-design-with-web-api-extensions-part-1-notifications/
六邊形架構:
https://fideloper.com/hexagonal-architecture
https://herbertograca.com/2017/09/14/ports-adapters-architecture/
製作人員
http://www.andreavallotti.tech/en/2018/01/event-source-and-cqrs-in-c/
https://www.exceptionnotfound.net/real-world-cqrs-es-with-asp-net-and-redis-part-1-overview/
https://buildplease.com/pages/fpc-1/
https://dotnetcoretutorials.com/2019/04/30/the-mediator-pattern-in-net-core-part-1-whats-a-mediator/
https://itnext.io/why-and-how-i-implemented-cqrs-and-mediator-patterns-in-a-microservice-b07034592b6d