長期以來我都在實踐OOP,進而經過OOP來實現DDD,特別是如何經過面向對象的技巧來創建一個領域模型。OO的一些特性在創建領域模型時顯得恰如其分,可否掌握OO的技巧,對建立領域模型有着相當重要的做用。
這篇文章爲你們介紹一種常見的函數式架構,特別是如何經過函數式語言來實現DDD,進而利用函數式組合的特性,建立函數pipeline。
軟件架構是圍繞着領域模型而作的若干設計,若是按照c4模型的定義,軟件架構由下面四個級別的架構組成的:html
領域驅動設計中有一半概念是在討論問題域,並非一上來就教你如何寫代碼,這說明理解一個問題域是複雜的,看清問題的本質是須要時間的。當你開始着手劃分限界上下文的時候,說明你已經對需求有了很好的瞭解。可是經驗告訴咱們,剛開始你的理解,每每都不是最終的需求,或者仍然須要屢次跟領域專家確認和交互,才能獲得最終的需求。
這個時候,若是你一上來就按照限界上下文劃分微服務,每每可能會步入Microservice Premium。
要想軟件在一開始就能達到快速試錯的目的,一上來就作微服務, 會讓步子邁得有點大。微服務架構帶來了分佈式的複雜性,使得前期生產效率大大下降,另外還存在船大難掉頭的狀況,一旦設計出現返工,生產效率也會打折扣。固然,這不是絕對的,若是架構師已經在該行業深耕多年,對業務更是瞭如指掌,項目一開始就設計爲微服務也何嘗不可。
在項目初期,在需求還不是很是明確的時候,你徹底能夠建立一個單體應用,而後經過不一樣的模塊或程序集來隔離不一樣的界限上下文,經過不斷的試錯和快速反饋來調整你的解決方案。
一種比較嚴格的說法是,當你關閉其中一個微服務,若是整個應用程序都崩了,其實你設計的不是一個微服務架構,而是一個分佈式單體應用程序。git
在過去的若干年裏,我常用一種叫「Layer architecture"的軟件架構, 這種架構每每把代碼分紅若干層:web
在問題域裏,各類業務之間的邊界是模糊的,限界上下文則是業務在解決方案上的映射,是人爲劃分的邊界。在邊界裏面的內容,是可信任和合法的,相反,界限外面的一切輸入,則是非法和不可信任的(圖3)。
這就要求咱們在限界上下文的邊界,引入驗證邏輯,從而阻止外部輸入,以及驗證對外部的輸出。
常見的驗證邏輯如:typescript
縱然,經過FP的代數數據類型(Algebraic data type)可以快速完成領域建模,可是咱們知道,領域模型不是靜態的,它是由一些列事件組成的過程。而這種轉化過程,正是領域模型狀態發生變化的過程,即狀態機(圖4)。
領域模型狀態轉換的過程跟實現語言無關,一個設計精良的領域模型,就比如一個狀態機。例如在買機票的過程當中,填寫我的信息,填寫聯繫人,選座,買保險和付款的過程,就是訂單狀態發生變化的過程。再好比用戶註冊的過程,填寫基本信息,驗證郵箱,也是用戶信息狀態發生變化的過程。以OO爲例,咱們習慣於經過增長標誌位的方式,進行領域建模:數據庫
type User = { name: string password: string email: Email | null isEmailVerified: boolean //當驗證完email後設置爲true canLogin: boolean //當email被驗證後方可login }
業務邏輯的實現過程,就是填充用戶屬性和修改標誌位的過程。然而,這種方式實際上存在若干問題:編程
按照上面的狀態從新對用戶建模,獲得的模型以下:json
type UnVerifiedUser = { name: string password: string } type VerifiedEmailUser = { name: string password: string email: Email } 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。整體來講,函數式語言的代數數據類型,以及函數式的一些思想,爲實踐領域驅動設計提供了其餘的選項。