長期以來我都在實踐oop,進而通過oop來實現ddd,特別是如何通過物件導向的技巧來建立乙個領域模型。oo的一些特性在建立領域模型時顯得恰如其分,能否掌握oo的技巧,對建立領域模型有著至關重要的作用。
這篇文章為大家介紹一種常見的函式式架構,特別是如何通過函式式語言來實現ddd,進而利用函式式組合的特性,建立函式pipeline。
軟體架構是圍繞著領域模型而做的若干設計,如果按照c4模型的定義,軟體架構由下面四個級別的架構組成的:
領域驅動設計中有一半概念是在討論問題域,並不是一上來就教你如何寫**,這說明理解乙個問題域是複雜的,看清問題的本質是需要時間的。當你開始著手劃分限界上下文的時候,說明你已經對需求有了很好的了解。但是經驗告訴我們,剛開始你的理解,往往都不是最終的需求,或者仍然需要多次跟領域專家確認和互動,才能得到最終的需求。
這個時候,如果你一上來就按照限界上下文劃分微服務,往往可能會步入microservice premium。
要想軟體在一開始就能達到快速試錯的目的,一上來就做微服務, 會讓步子邁得有點大。微服務架構帶來了分布式的複雜性,使得前期生產效率大大降低,另外還存在船大難掉頭的情況,一旦設計出現返工,生產效率也會打折扣。當然,這不是絕對的,如果架構師已經在該行業深耕多年,對業務更是瞭如指掌,專案一開始就設計為微服務也未嘗不可。
在專案初期,在需求還不是非常明確的時候,你完全可以建立乙個單體應用,然後通過不同的模組或程式集來隔離不同的界限上下文,通過不斷的試錯和快速反饋來調整你的解決方案。
一種比較嚴格的說法是,當你關閉其中乙個微服務,如果整個應用程式都崩了,其實你設計的不是乙個微服務架構,而是乙個分布式單體應用程式。
在過去的若干年裡,我經常使用一種叫「layer architecture"的軟體架構, 這種架構往往把**分成若干層:
在問題域裡,各種業務之間的邊界是模糊的,限界上下文則是業務在解決方案上的對映,是人為劃分的邊界。在邊界裡面的內容,是可信任和合法的,相反,界限外面的一切輸入,則是非法和不可信任的(圖3)。
這就要求我們在限界上下文的邊界,引入驗證邏輯,從而阻止外部輸入,以及驗證對外部的輸出。
常見的驗證邏輯如:
縱然,通過fp的代數資料型別(algebraic data type)能夠快速完成領域建模,但是我們知道,領域模型不是靜態的,它是由一些列事件組成的過程。而這種轉化過程,正是領域模型狀態發生變化的過程,即狀態機(圖4)。
領域模型狀態轉換的過程跟實現語言無關,乙個設計精良的領域模型,就好比乙個狀態機。例如在買機票的過程中,填寫個人資訊,填寫聯絡人,選座,買保險和付款的過程,就是訂單狀態發生變化的過程。再比如使用者註冊的過程,填寫基本資訊,驗證郵箱,也是使用者資訊狀態發生變化的過程。以oo為例,我們習慣於通過增加標誌位的方式,進行領域建模:
type user =
業務邏輯的實現過程,就是填充使用者屬性和修改標誌位的過程。然而,這種方式實際上存在若干問題:
按照上面的狀態重新對使用者建模,得到的模型如下:
type unverifieduser =
type verifiedemailuser =
type user =
| unverifieduser
| verifiedemailuser
如果有更多的使用者狀態,你還可以持續新增到user型別中。
這種通過"|"建立的user型別被稱為在fp中被稱為union型別,也叫product或sum型別, 在typescript被稱為discriminated union。這時候的user型別,可以用來在領域模型中實現領域邏輯,通常這種union型別需要配合模式匹配來完成,例如修改密碼,登入,修改郵件位址等邏輯,都是針對user型別做模式匹配的過程。關於模式匹配的用法,在此不再細說。
這種通過狀態機的方式,實現業務邏輯時有下面幾個好處:
函式式程式設計的乙個主要目標就是讓**有**性,通過函式簽名理解函式的用途。為了達到這個目的,函式式語言設計了若干特性,例如不可變的資料結構,還有各類monad來避免***。在ddd實踐中,應該避免i/o相關的**出現domain中。例如讀寫資料庫,呼叫第三方系統的api等相關**,需要把這類具有***的**推到domain的外圍。如果需要做的更好,那就必須使用cqrs加event sourcing。我在之前一篇文章提到過這個觀點,不過部分讀者沒有理解其中的意思,我在這裡再做一些說明。首先,cqrs不僅僅是為了讀寫分離,從而提高讀寫效能。讀模型和寫模型(領域模型)的分離意味著職責也是分離的,從而在設計領域模型的時候,打消對查詢效能的考慮,有助於設計出純淨的領域模型。當然僅靠cqrs還是不夠的,有些時候任然無法完全脫離資料庫的考慮,因為領域模型始終是要持久化在資料庫裡,你就要考慮資料庫相關的約束,例如主外來鍵,如何建表,如何高效儲存乙個列表等。而持久化乙個event則完全擺脫了資料庫技術,因為乙個event就是乙個json, 只有這樣才能設計出理想的領域模型。當然引入cqrs和es在專案初期成本略高,不再詳細描述。
以api為例,乙個完整的使用者請求就是乙個pipeline(圖6)。
假設每一步都是有若干個函式組成,我們能夠將他們組合到一起嗎?答案是很難,主要原因如下:
這篇文章總結了一些使用函式式語言實踐ddd的大致思路,也為函式式架構提供了一些參考。由於篇幅的原因,並沒有介紹到ddd的方方面面,同時,一些實現細節則是點到為止,例如如何使用monad。總體來說,函式式語言的代數資料型別,以及函式式的一些思想,為實踐領域驅動設計提供了其他的選項。
使用函式式程式語言F 編寫DDD與CQRS應用
在近期的一次演講中,lev gorodinski提出了一種觀點,他認為對於領域驅動設計 ddd 來說,如果使用f 這種函式式程式語言來代替c 這種物件導向程式設計語言進行開發的話,能夠帶來兩個額外的好處,這種方式不僅專注於行為,而且能夠更多地使用宣告方式來表現 講座中使用的示例用到了包括事件溯源 e...
Flutter多語言實踐
flutter的多語言文件 英文版 中文版簡單來講,流程圖如下 簡單講一下整個流程 string order list waiting for review intl.message waiting for the review name order list waiting for review ...
系統多語言實踐 二
1.多語言儲存 假設下面乙個場景 系統有乙個產品目錄需要維護,目錄名稱和描述需要支援多語言儲存。表結構設計如下 product category pk欄位 型別允許null 描述pk id varchar 36 n 類別id lang id varchar 36 n多語言id created on ...