[IT] DDD, Hexagonal, Onion, Clean, CQRS 大整合

本文是介紹我如何將 DDD, Hexagonal, Onion, Clean, CQRS Architecture 等架構概念整合在一起,我將它命名為 Explicit Architecture。上述的概念基本上都是通過了市場的試驗,並在許多高要求的平台上被應用。 系統的基本組件 首先回顧 EBI 與 Ports & Adapter 架構。這兩種架構都明確區分了哪些程式碼是應用程式的內部,哪些是外部,以及哪些是連接內部和外部的程式碼。 Ports & Adapters 明確地定義出了系統的三個部分: 使用者介面 (User Interface, UI) 商業邏輯(business logic)、應用程式核心(application core) 基礎設施(Infrastructure),如 DB、搜尋引擎或第三方API等工具。 我們真正應該關心的是應用程式的核心,這是讓我們的程式碼能夠完成其應有功能的程式碼。它可能會使用多種 UI(網頁、手機、CLI、API 等等),但實際執行工作的程式碼是相同的,並位於應用程式的核心,觸發它的 UI 實際上並不重要。 一個典型的應用程式流程從 UI 的程式碼開始,經過應用程式核心到基礎設施程式碼,再回到應用程式核心,最後將回應傳遞給 UI。 工具 Tools 工具指的是那些遠離我們系統核心程式碼,但為我們應用程式所用的工具,例如,DB、搜尋引擎、網頁伺服器或 CLI 控制台(儘管後兩者也是交付機制)。 雖然將 CLI 與 DB 分類在一起可能有些奇怪,儘管它們有不同的目的,但實際上它們都是應用程式使用的工具。關鍵的區別在於,CLI 和網頁服務器用於告訴我們的應用程式做些什麼,而 DB 則由我們的應用程式告訴它做些什麼。這是一個非常重要的區別,因為它對我們如何建構連接這些工具與應用程式核心的程式碼有著強烈的影響。 將工具和傳遞機制連接到應用程式核心 連接工具與應用程式核心的程式碼單元被稱為適配器(Ports & Adapters Architecture),適配器實現了將業務邏輯與特定工具進行通訊。 告知我們應用程式應該做什麼事的適配器稱為 Primary 或 Driving Adapters; 被我們應用程式告知應該做什麼事的適配器稱為 Secondary or Driven Adapters。 埠 Ports 然而,這些適配器並非隨機創建的,它們是為了適應應用程式核心的一個非常特定的入口點,也就是埠。埠不過是一種規範,說明工具如何使用應用程式核心,或者說明它如何被應用程式核心使用。在大多數語言中,以其最簡單的形式,這種規範,或埠,即是一個介面(interface),但實際上可能由多個介面和 DTO 組成。...

<span title='2023-11-06 23:38:13 +0800 +0800'>November 6, 2023</span>&nbsp;·&nbsp;2 min&nbsp;·&nbsp;hgraca

[IT] 服務導向的架構 Service Oriented Architecture (SOA)

The SOA Style has been around since the late 1980s and has its origins in ideas introduced by CORBA, DCOM, DCE and others. Much has been said about SOA, and there are a few different implementation patterns but, in essence, SOA focuses on only a few concepts and doesn’t give any prescription on how to implement them: SOA風格自 1980 年代晚期便一直存在,其起源於 CORBA、DCOM、DCE 等等。關於 SOA 已經有很多討論,且有許多不同的實作,但本質上,SOA 只關注少數幾個概念,而且指引我們如何去實作: 使用者導向的應用程式。 可重複使用的商業服務。 獨立的技術堆。 自治性(獨立演進、可擴展性與可部署性) SOA 是一最獨立於任何技術或產品的架構原則,就像多態與封裝一樣。...

<span title='2023-11-02 22:32:25 +0800 +0800'>November 2, 2023</span>&nbsp;·&nbsp;5 min&nbsp;·&nbsp;hgraca

[IT] 從 CQS 到 CQRS

當我們有一個以數據為中心的應用程式,即只實現基本的 CRUD 操作,並將業務流程(即要更改的數據和更改的順序)留給用戶時,好處是用戶可以在不需要更改應用程式的情況下更改業務流程。另一方面,這意味著所有用戶都需要知道所有可以使用該應用程式執行業務流程的所有細節,這在沒有明確的規範且有大量人員參與其中時,將會是一個大問題。 在一個以數據為中心的應用程式中,該應用程式對業務流程一無所知,因此該 domain 無法擁有任何「動詞」,也就是說,應用程式本身無法做出除了改變原始數據以外的任何事情。它變成了數據模型(data model)的高度抽象。這些流程只存在於應用程式用戶的腦海中,或者甚至存在於釘在電腦螢幕上的便利貼中。 一個非凡且實用的應用程式旨在減輕使用者的「流程」負擔,透過捕捉他們的意圖,使其成為一個能夠處理行為的應用程式,而不僅僅是儲存數據。 CQRS is the result of an evolution of several technical concepts that work together to help provide the application with an accurate reflection of the domain, while overcoming common technical limitations. CQRS 是許多技術概念演變的結果,這些概念使應用程式能準確地反映領域(domain),並同時克服常見的技術限制。 命令查詢分離 Command Query Separation 正如 Martin Fowler 所述,「命令查詢分離」這個術語是由 Bertrand Meyer 在他的《物件導向軟體建構(Object Oriented Software Construction)》(1988年)中首次提出的 - 這本書被認為是物件導向早期最具影響力的書籍之一。 梅爾認為,作為一個原則,我們不應該有既改變數據又返回數據的方法。因此,我們有兩種類型的方法: Queries(查詢):返回數據但不更改數據,因此沒有副作用; Commands(指令):更改數據,但不返回數據。 換句話說,提問不應改變答案,而行動也不應回饋答案,這同時也有助於尊重單一責任原則。 然而,有些模式是這條規則的例外,傳統的佇列和堆疊會彈出在佇列或堆疊中的元素,既改變了佇列或堆疊,也返回了從中移除的元素。 命令模式 Command Pattern 命令模式的主要概念是將我們從資料中心的應用程式轉移到以流程為中心的應用程式,具有領域知識和應用程式流程知識。 在實際操作中,這意味著我們不再讓使用者執行 CreateUser, ActivateUser 和 SendUserCreatedEmail 這三個動作,而是讓使用者直接執行一個 RegisterUser 的指令,這個指令將會執行前述的三個動作,作為一個封裝的業務流程。...

<span title='2023-11-01 23:29:40 +0800 +0800'>November 1, 2023</span>&nbsp;·&nbsp;2 min&nbsp;·&nbsp;hgraca

[IT] 事件驅動架構 Event-Driven Architecture

利用事件來設計應用程式似乎是 80年代後期開始的一種做法,我們可以在使用事件在前端或後端任何地方使用事件,當按下一個按鈕,涉及某些數據變更,或是執行某些後端動作時。 What/When/Why 就像類別(classes)一樣,組件(components)之間應該保持低耦合,但在內部應保持高內聚。當組件需要協作時,比如說組件A需要觸發組件B中的某些邏輯,自然的做法就是讓組件A調用組件B中一個物件的方法。然而,如果A知道B的存在,那麼它們就是耦合的,A依賴於B,這使得系統更難改變和維護,事件可以用來防止耦合。 如果我們有一個團隊只專注於組件B的工作,它可以改變組件B對組件A邏輯的反應,甚至不需要與負責組件A的團隊溝通。組件可以獨立進化:我們的應用程式變得更有機(organic)。 即使在同一個組件中,有時我們會需要執行程式碼作為一個行動的結果,但它並不需要立即執行,也就是說,當事件的結果互不影響的情境下,我們可以採用 異步(async) 的策略執行程式。 然而,這樣做也存在危險,如果我們不加選擇地使用事件,可能會使一個概念上高度內聚的邏輯被解耦。換句話說,本應在一起的程式碼被強行分開,變得很難追蹤、理解(類似goto語句),最後使得它變成:speghetti code! 為了防止我們的程式碼變成一堆混亂的 speghetti code,我們應該清楚的限制事件的使用規則。根據我的經驗,有三種情況下應該使用事件: 解耦元件。 執行異步任務。 追蹤狀態變更 (audit log) 1. 解耦元件 當元件A執行需要觸發元件B邏輯的動作時,我們可以選擇不直接呼叫它,而是將一個事件發送到事件調度器(dispatcher)中。元件B將會在調度器中監聽該特定事件,並在事件發生時作出反應。 這意味著A和B都將依賴於調度器和事件,但他們將對彼此一無所知,也就是說他們是解耦的。 理想情況下,調度器和事件都不應存在於任何組件中: 調度器應該是一個與我們的應用程式完全獨立的庫,因此應該使用依賴性管理系統安裝在一個通用的位置。在PHP世界中,我們會使用 Composer 將之安裝在 vendor 的資料夾。(C# 可以參考我 EventBus 的文章) 這個事件雖然是我們應用程式的一部分,但應該存在於兩個組件之外,以保持它們對彼此一無所知。該事件在組件之間共享,並且是應用程式核心的一部分。事件是 DDD 所稱的 共享核心(Shared Kernel) 的一部分。這樣,兩個組件將依賴於共享核心,但將對彼此保持不知情。然而,在單體應用程式中,為了方便,可以將其放置在觸發事件的組件中。 Shared Kernel 共享核心 […] Designate with an explicit boundary some subset of the domain model that the teams agree to share. Keep this kernel small. […] This explicitly shared stuff has special status, and shouldn’t be changed without consultation with the other team....

<span title='2023-10-31 23:25:09 +0800 +0800'>October 31, 2023</span>&nbsp;·&nbsp;2 min&nbsp;·&nbsp;hgraca

[IT] 乾淨架構 Clean Architecture

Robert C. Martin (aka Uncle Bob) 在 2012 年在他的部落格上發表了他對於乾淨架構的想法,並在幾個會議上進行了關於乾淨架構的演講。 乾淨架構套用了許多為人熟知的概念、規則和模式,並解釋如何將它們組合在一起,以提出一種標準化的應用程式建構方式。 站在 EBI, Ports & Adapters 與洋蔥架構的肩膀上 乾淨架構背後的核心目標與 Ports & Adapters(六邊形)和洋蔥架構的目標是相同的: 工具的獨立性。 交付機制的獨立性。 獨立測試的可行性。 在發布有關乾淨架構的文章中,這是用來解釋整體概念的一張圖: 正如 Uncle Bob 在他的文章中所說,上面這張圖嘗試將最新的架構思想整合成一個可行的概念。 讓我們將乾淨架構的圖表與用來解釋六角架構和洋蔥架構的圖表進行比較,看看它們在哪些地方相符: 工具和交付機制的外部化 六角形架構專注於將工具和交付機制從應用程式中外部化,使用介面(ports))和適配器(adapters)。這也是洋蔥架構的核心價值之一,如圖所見,UI、基礎設施和測試都在圖表的最外層。乾淨的架構具有完全相同的特性,將 UI、Web、DB 等都放在最外層。最後,所有應用程式核心程式碼都是與框架、庫獨立的。 依賴方向 在六角架構中,我們並沒有任何明確的指示告訴我們依賴性的方向。然而,我們可以輕易地推斷出來:應用程式有一個埠(或介面),必須由一個適配器來實現或使用。因此,適配器依賴於介面,它依賴於位於中心的應用程式。外部的東西依賴於內部的東西,依賴性的方向是朝向中心。在洋蔥架構圖中,我們也沒有任何明確的指示告訴我們依賴性的方向,然而,在他的第二篇文章中,Jeffrey Palermo 非常清楚地說明所有的依賴性都是朝向中心。乾淨架構則是非常明確地指出依賴性方向是朝向中心。他們都在架構層面引入了依賴反轉原則。內圈中的任何東西都不能知道外圈中的任何東西。此外,當我們跨越邊界傳遞數據時,它總是以對內圈來說最方便的形式存在。 分層 六角形架構圖只顯示了兩層:應用程式的內部和外部。然而,洋蔥架構則將 DDD 中 application layer 融入其中:application service 持有用例邏輯(use case logic);domain service 封裝不屬於實體或價值對象的領域邏輯。與洋蔥架構相比,乾淨架構保留了 application layer(use case)和 entities layer,但似乎忽略了 domain service layer。然而,閱讀 Uncle Bob 的文章後,我們意識到他認為一個 entity 不僅是 DDD 意義上的 entity,而且是任何 domain object:「一個實體可以是一個帶有方法的物件,或者可以是一組數據結構和函數。」實際上,他合併了這兩個最內層的層級以簡化圖表。 獨立測試性 在所有三種架構風格中,他們遵循的規則為應用程式和領域邏輯提供了隔離。這意味著在所有情況下,我們都可以簡單地模擬外部工具和傳遞機制,並在隔離中測試應用程式程式碼,而無需使用任何數據庫或 HTTP request。...

<span title='2023-10-29 21:11:58 +0800 +0800'>October 29, 2023</span>&nbsp;·&nbsp;1 min&nbsp;·&nbsp;hgraca