關於領域驅動設計的函式程式設計思考 - Naveen Negi

banq發表於2019-05-03

在過去的幾年裡,我一直在使用像Elixir和Clojure這樣的函式式語言,即使我確信DDD可以應用於函式式語言,但這個領域並沒有足夠的資源介紹。嗯,也就是很少的相關討論和博文,但大多數人又試圖將DDD模式從OO直接對映到FP。

戰略與戰術模式

DDD分為戰略模式和戰術模式,戰略模式由有界上下文,無所不在的語言和上下文對映組成,而戰術模式由值物件,實體和聚合等概念組成。

戰略模式很容易對映到任何語言,它主要涵蓋更高階別的軟體設計,如如何建立有界上下文,如何根據它們之間的關係整合這些有界上下文,以及如何通過上下文圖對映這些關係。這些模式非常有用,不依賴於所使用的程式語言或框架。

然而,在實現戰術模式時會產生混亂,因為它們的實現取決於所使用的程式語言。

OO更喜歡將資料和行為(方法)保持在一起(物件),因為期望物件具有狀態,並且所有改變內部狀態的操作必須由物件本身提供(通過“xx.method()”表示法)。但是,預設情況下,函數語言程式設計語言是不可變的,將函式和資料結構儲存在不同的模組中是很常見的。我花了一段時間才意識到FP中不存在貧血領域模型這個概念。

聚合

個演講中,Ritch Hickey談到了聚合而沒有提到DDD,但我認為他提供了一個非常好的Aggregates解釋。為Clojure(或任何其他功能語言)實現它們提供了一個很好的指導。

聚合背後的想法是強制一致性和不變數。

聚合是強制執行不變數的位置,並充當一致性邊界。更新聚合的一部分時,可能還需要更新其他部分以確保一致性。

我在從OO到FP轉換過程中遇到的一個誤解是隻考慮資料,因為資料和行為總是在OO中共存;但是,在FP中,您傾向於將資料和功能函式分開。

因此,在FP中實現Aggregate的關鍵是在資料和函式兩方面考慮聚合。嘗試將聚合建模為一組函式,例如,如果您有訂單和訂單行等實體,其中每個訂單可以包含一個或多個訂單行,那麼訂單Order將是您的聚合根,幷包含一組強制執行一致性和不變數的函式。假設您有一條規則,即每個訂單至少有一個訂單行。然後,如果您嘗試刪除多個訂單行,則聚合可確保在只剩下最後一行時會報出錯誤。

我們是否需要區分值物件和FP中的實體?

DDD的經典(讀取物件導向)實現基於其可變性和身份標識概念來區分值物件型別和實體型別。值(物件)型別是不可變的,並且不會在其中傳達足夠的資訊,例如,Color可以是值型別,其中Color型別本身不具有任何含義,但是當附加到實體時如襯衫或汽車(例如紅色襯衫或黑色汽車)就代表他們在你的領域意味著什麼。

相反,一個實體有一個生命週期。這些是可變的型別,並通過不同的生命週期事件進行更改,例如,Order可以是經歷不同生命週期事件的實體,例如新增Item到Order中或從Order中刪除Item。每個生命週期事件都會改變實體。

在FP中,預設情況下一切都是不可變的,這導致我們錯誤地認為我們不需要區分值型別和實體。正如我將在以下部分中解釋的那樣,如果要構建無限可伸縮的應用程式,這種區別以及其他DDD模式是至關重要的。

建模聚合

無論您在軟體應用程式增長時是使用FP還是OO,您都可能最終對資料庫進行分割槽,這實際上意味著以前存住在同一臺機器(或機器叢集)中的實體/聚合現在生存在不同的機器中。關於實體位置的應用程式程式碼的任何假設可能不再適用,並且在單個事務中修改多個實體的任何嘗試都將面臨分散式事務的危險。

以下是關於如何避免這些陷阱的指導原則(靈感來自Pat Helland的這篇著名論文)

  1. 聚合是作為事務邊界的:每個聚合用作事務邊界。這個Aggregate的唯一標識就是事務的範圍邊界,不要嘗試在一個事務範圍中放置多個聚合,因為如果這些聚合移動到不同的計算機,則無法保證事務的成功。
  2. .訊息傳送到Aggregate:無論您是構建微服務還是單體Monolith應用程式,重要的是我們不要假設其他聚合的存在。每個聚合應通過向其他聚合的地址傳送訊息來與另一個聚合進行通訊,聚合地址是聚合的唯一標識身份ID。遵循這種方法,應用程式可以大規模擴充套件。良好的聚合建模實際上是聚合到聚合的訊息傳遞。聚合傳送一條訊息給另一個聚合。接收方d的聚合包括上層(域層)業務邏輯和下層(基礎架構層),但兩個聚合之間的通訊始終位於域層。
  3. Aggregate表示不相交的資料集:看到不少反模式,其中兩個聚合共享模型或永續性模式,或者有時存在對屬於另一個聚合的模型的隱藏的延遲載入。這些是我們應該不惜一切代價避免的陷阱。您可以通過“伸縮規模不可知的程式設計抽象”來實現所有這一切,這意味著,您將應用程式劃分為有意義的完整聚合,每個聚合通過聚合邊界(與規模無關的層)與另一個聚合進行對話,並且不直接訪問任何模型或資料庫。一個聚合的資料必須與其他聚合的資料不相交。

DDD和開發人員的心態

我見過許多開發人員考慮與OO有關的禁忌。他們甚至不想談論DDD,因為它太過分了。然而,我們中的許多人都不理解OO僅僅是一個解決問題的工具,並且使工具失去信譽並沒有使問題消失。例如,Elixir和Clojure中的協議是解決程式碼可擴充套件性問題的一種方法,我看到很多開發人員都不願意使用它,因為程式碼可擴充套件性聽起來如此OOish。

下面這個針對Clojure協議的StackOverflow答案清楚地解釋了它。

Clojure中協議的目的是以有效的方式解決表示式問題。

那麼,表達問題是什麼?它指的是可擴充套件性的基本問題:我們的程式使用操作來操縱資料型別。隨著我們的程式的發展,我們需要使用新的資料型別和新操作來擴充套件它們。特別是,我們希望能夠新增與現有資料型別一起使用的新操作,並且我們希望新增與現有操作一起使用的新資料型別。而且我們希望這是真實的擴充套件,即我們不希望修改現有的程式,我們希望尊重現有的抽象,我們希望我們的擴充套件是單獨的模組,在單獨的名稱空間中,單獨編譯,單獨部署,單獨型別檢查。我們希望它們是型別安全的。[注意:並非所有這些都適用於所有語言。但是,例如,即使在像Clojure這樣的語言中,使它們具有型別安全性的目標也是有意義的。僅僅因為我們無法靜態檢查型別安全並不意味著我們希望我們的程式碼隨機中斷,對吧?

類似地,實體,值(物件)型別,聚合的概念被FP開發人員嘲笑,因為這些概念只用於OO。

相關文章