基於函數語言程式設計的領域驅動設計 - Scott Wlaschin
Scott Wlaschin 是一名開發人員、架構師和作家。他是流行的 F# 網站 fsharpforfunandprofit.com 的作者,以及 Pragmatic Bookshelf 出版的《Domain Modeling Made Functional》一書的作者。Scott 以其對函數語言程式設計的非學術方法而聞名。
以下是摘要,原文點選標題
在一個經典的數學函式中,事物是不會改變的。例如,當你說二加二時,當你做加減乘除時,數字二不會改變。你會有一個新的數字,你不會改變原來的數字,日期也是如此。當你在一個日期上新增一個時間,你不會修改原來的日期。
如果你把這個邏輯應用到所有的事情上,那麼什麼都不會改變,你總是創造新的東西。
這使得理解程式變得更加容易,因為你總是知道你所做的事情沒有被修改,你總是有新的事情要做。
那麼,為什麼現在使用函數語言程式設計?因為這種不變性,也因為這種組合,沒有繼承性。
因此,一切都趨向於非常明確:
每一個引數都是明確地傳入的。
函數語言程式設計沒有正式的定義。我的定義是,它是一種程式設計風格,你用函式來做一切事情。
在物件導向程式設計中,基本單元是一個類。而在函數語言程式設計中,基本單元是一個函式。在函數語言程式設計中,如果你想改變某個東西的行為,比如說策略模式,你就傳入一個函式。如果你想組成一個更大的函式集,你就把兩個函式組合在一起。
基本上,你所做的一切就是函式。你拿著這些函式,把它們連鎖在一起,典型地,變成某種管道。你甚至可能有一個函式,輸入是一個函式,而輸出是一個函式。所以你可以把它叫做函式轉換器。
在基於類的程式設計中,有很多隱含的程式碼。我是說,你可以有一個沒有引數的方法。它返回void,沒有引數。在幕後,它正在做一些事情,因為它正在使用例項變數或其他東西。但你不能真正看到它在做什麼。而這正是物件導向程式設計的全部意義所在。
物件導向的整個要點是封裝。你應該把它的工作隱藏起來。
函數語言程式設計則正好相反。它的理念是所有的東西都是透明的。所有的東西都是公開的。沒有隱藏的資料。
一般來說,當你在編碼時,如果所有的東西都是不可變的,那麼人們是否能看到一個物件的內部結構其實並不重要,因為他們不能改變它。
不可變性的一個方面是,如果所有的東西都是不可變的,它也能很好地支援併發性。
所以你不會有副作用,你也不必擔心多個程式訪問同一個例項。
雖然我不得不說這有點誇大其詞。我的意思是,那是他們對函數語言程式設計誇大的事情之一,就是它的併發性非常好。它在處理方面做得非常好,它確實如此。但即使它沒有,我也不認為那是使用它的主要原因。
主要原因是你用函式式程式編寫的程式,我認為它們往往更清晰,更容易理解。
有時使用物件,你往往會得到我所說的物件湯,即一個物件與另一個物件呼叫,後者與另一個物件呼叫,後者與第一個物件呼叫。所有這些物件都在相互呼叫,真的很難將它們分開。如果你曾經除錯過這樣的東西,那真的很痛苦。
函數語言程式設計在某種程度上不會這樣做,因為它的壓力是讓事情保持簡單。當每一個引數都被傳入時,要有一百個引數真的很困難。有一種自然的壓力,讓事情保持簡單,讓小的元件粘在一起。(banq注:函式管道彼此連結是一個方向的依賴呼叫,比物件正反兩個方向的呼叫依賴要簡單一半)
DDD與FP
領域驅動設計並不依附於任何特定的程式設計正規化。它真的不應該是這樣。領域驅動設計的理念是,你關注領域而不是技術。所以,你嘗試著關注,使用領域中的詞彙。你試著在程式碼中對領域進行建模,並且你試著不要用各種與人無關的技術東西來汙染你的領域。因此,它們與你使用哪種程式設計正規化沒有關係。
在物件導向的程式設計中,很容易有很多額外的複雜性,或者人們所說的儀式,像子類和所有這些東西的額外東西。而且很容易出現有很多東西在裡面的程式碼,而這些東西並不是領域的真正組成部分。在現實世界中,你沒有基類。你沒有代理類。你沒有經理類。你沒有介面。現實世界的物件沒有這些。
(banq注:物件導向恰恰是對現實的對映,Object是客體物件,周圍都是Object,這是一種隱喻修辭手法,理工科讀點莎士比亞文科還是很重要:資料模型是一種隱喻修辭手法)
在函數語言程式設計中,它往往要簡單得多,因為函數語言程式設計沒有類。所以這些東西都不會發生。它基本上有資料,並且有對資料的操作。
很多人在做領域建模的時候,都會把注意力放在資料方面的事情上。這樣你就自然變成資料庫驅動的設計,你從資料庫表開始,然後再從那裡開始。
(banq注:資料庫時代的終結 ; 2012年Robert C. Martin(鮑勃大叔)的NO DB)
人們已經意識到,在分析一個真實的系統時,企業所關心的是事件(banq注:領域事件),是行動,是工作流程,是活動。
這些才是真正重要的東西,而資料只是傳輸東西的一種方式(banq注:用事件替代你的DTO資料結構)。
“活動”你想要建模的,你要建模的是一個“有輸入的活動”。
一個函式也正是如此:它是一個輸入和輸出。
所以,函式對於領域建模來說實際上是非常適合的。
在FP中建立戰術設計模型
DDD中很多都是非常物件導向的,如實體和聚合體,這些是低層次戰術的領域驅動設計。
值物件在DDD中是一個物件,如果你有一個不同的物件,裡面有相同的資料,那麼它就是相同的東西。
在函數語言程式設計中,那是預設的,
但是在物件導向程式設計中,這並不是預設的。
在物件導向程式設計中,每個物件都有一個不同的引用。
因此,如果你想讓兩個東西相等,你必須覆蓋等價方法和獲取雜湊碼方法以及所有這些其他的東西。
在物件導向程式設計中,實體被認為是可變的物件。
在函數語言程式設計中,它們是不可變的。
我不喜歡有這些行話。我認為這有損於讓人們理解這些東西,但你完全可以以不同方式做這些相同的事情。
不過,我應該指出的一點是,這個在領域驅動設計中使用的資源庫倉儲Repository模式,實際上是在領域驅動設計之外使用的。
在物件導向的程式設計中,把資料庫訪問和程式碼以及業務物件混在一起是非常常見的。你可能有一個物件可以自己儲存。
人們幾乎從不在函數語言程式設計中使用它。
在函數語言程式設計中,[儲存庫模式]會被認為是一種反模式。這將被認為是一件非常糟糕的事情。
因為在函數語言程式設計中,我們傾向於把可預測的東西和不可預測的東西分開,把確定性的程式碼和非確定性的程式碼分開。
我們所說的純函式是一個總是給出相同結果的函式。對於相同的輸入,它總是給出相同的結果。因此,兩個數字相加無論怎麼加總是給出相同的結果。
現在,從資料庫中讀取東西就不是這樣了。
如果我再次從資料庫中讀取,我可能會得到一組完全不同的資料。
(banq注:資料庫是可變資料儲存,每次從資料庫讀取類似2+2再做一次,2+2做了兩次是8,肯定不是2+2=4做了一次,這裡採取比喻有些漏洞,缺失上下文的對比)
在函數語言程式設計中,我們傾向於將任何與資料庫、檔案系統或網路有關的東西分開。
我們試圖把它移到核心業務邏輯之外。
因此,我們把核心業務邏輯放在中間,而任何與外部世界有關的東西都在應用程式的邊緣。
(banq注:鮑勃大叔clean架構,實際是將導致狀態變化的副作用與正作用分離:副作用是程式設計頭號敵人!如何剝離它?)
核心業務邏輯在中間,資料庫在外面,因此,它與經典的N層架構完全相反,在那裡你的資料庫層在底部。
在函數語言程式設計中,資料庫是在外面,而不是在裡面。
人們一直在討論其他架構中的這個問題:有一個埠和介面卡的架構、洋蔥架構、清潔Clean架構、六邊形架構。所有這些東西都是為了嘗試把領域邏輯放在中間,而把世界的其他部分放在外面。
有趣的是,在物件導向中,人們必須被鼓勵這樣做(banq注:人們預設卻不是這麼做,不是將業務和技術分離開來,而是按照 表現層 ->應用層 ->領域層 ->基礎設施層這樣N層架構,也是Evans原書提出的,但是不要忘記,Evans更多是領域專家,他只是借鑑當時他寫書時那個時代的技術架構)
但在函數語言程式設計中,它自動發生,這是預設的行為。
有一些語言,你可以做一些事情,但它並不真正鼓勵你去做正確的事情。所以很容易犯錯。
我喜歡這樣的語言,你真的被強迫做正確的事情。因此,一種語言是不可改變的,並且不讓你做資料庫的事情。它迫使你把資料庫的東西與核心業務邏輯分開。因此,這就是我喜歡函式語言的原因。它們真的鼓勵你以某種方式寫程式碼。
建模
在領域驅動設計中,有兩個邊界:
第一個邊界是他們所說的有邊界的上下文。
有界限的上下文的概念是你把你的程式分成子系統。
但每個子系統只能做好一件事,這個子系統中的一切都使用相同的術語,以相同的方式工作。
這聽起來非常明顯,但寫起來卻不是非常容易。
我見過很多大的系統,所有的東西都糾纏在一起,你有一些程式碼的碎片,它們都在一個大的混亂中互動,大的泥球。
(banq注:RefactorFirst:尋找Java程式碼庫中無所不包的大型“上帝”類)
在一個有邊界限制的環境上下文中,你需要保證某些約束或完整性得到滿足。
你可以用一個函式來強制執行:如果你有一個輸入和輸出,你就可以說,好吧,你改變這個東西的唯一方法就是透過這個函式。
而如果在可變的物件中非常常見的做法是:因為你可以改變任何欄位。變更一個欄位,然後忘記變更其他欄位,這是很容易的。而這可能是錯誤的。如果你有不可變的資料,你就不會有這個問題。如果你不能改變數值,那麼一旦它是正確的,那麼它就永遠不會改變。
實際上,它最終會減少很多防禦性程式設計。如果你有不可變的資料,你通常會在程式的最開始,在程式的邊緣驗證一些東西。然後一旦你進入了你的程式,你基本上就相信它是有效的,因為它不可能被改變。
其他還有F的介紹,更多點選標題見原文
相關文章
- 關於領域驅動設計的函式程式設計思考 - Naveen Negi函式程式設計
- 什麼是DDD領域驅動設計的統一語言?
- 理解領域驅動設計
- MasaFramework -- 領域驅動設計Framework
- 領域驅動設計示例
- 函數語言程式設計函數程式設計
- DDD領域驅動設計:領域事件事件
- 領域驅動設計與模型驅動設計的關係模型
- Scala 函數語言程式設計(一) 什麼是函數語言程式設計?函數程式設計
- 關於函數語言程式設計的思考(1)函數程式設計
- 關於函數語言程式設計的思考(2)函數程式設計
- 微服務架構設計基礎之領域驅動設計微服務架構
- JavaScript中的領域驅動設計JavaScript
- 領域驅動設計中的模型模型
- RAC的函數語言程式設計函數程式設計
- 領域驅動設計簡介
- 實現領域驅動設計
- 領域驅動設計核心概念
- 函數語言程式設計,真香函數程式設計
- Java 函數語言程式設計Java函數程式設計
- javascript函數語言程式設計JavaScript函數程式設計
- 初探函數語言程式設計函數程式設計
- 函數語言程式設計初探函數程式設計
- JavaScript 函數語言程式設計JavaScript函數程式設計
- Reactor事件驅動的兩種設計實現:物件導向 VS 函數語言程式設計React事件物件函數程式設計
- .NET併發程式設計-函數語言程式設計程式設計函數
- 函數語言程式設計-鏈式程式設計RAC函數程式設計
- 重讀領域驅動設計——如何說好一門通用語言
- 領域驅動設計戰術模式--領域事件模式事件
- 戲說領域驅動設計(廿五)——領域事件事件
- 問題驅動設計與領域驅動設計的區別 - abdullin
- JavaScript中的函數語言程式設計JavaScript函數程式設計
- C++的函數語言程式設計C++函數程式設計
- python的函數語言程式設計Python函數程式設計
- JavaScript 中的函數語言程式設計JavaScript函數程式設計
- 函數語言程式設計 VS 物件導向程式設計函數程式設計物件
- iOS鏈式程式設計及函數語言程式設計iOS程式設計函數
- 淺談DDD(領域驅動設計)