DDD是「Domain Driven Design」的縮寫,在中文中常被翻譯為領域驅動設計。今天我們就來介紹一下php中的DDD,有需要的可以參考參考。
DDD 是 Domain Driven Design 的縮寫,在中文中常被翻譯為領域驅動設計。在我們了解 DDD 是什麼之前,先討論下它不是什麼。
DDD 不是軟體框架。但基於 DDD 思想的框架是存在的,例如 Axon,它是以 DDD 為指導思想,使用 Java 實現的微服務軟體框架。
DDD 不是軟體設計模式。它不是像工廠,單例這樣子的設計模式。但 DDD 思想中提出了諸如資源庫(Repository)之類的設計模式。
DDD 不是系統架構模式。它不是像 MVC 這樣的架構模式。但 DDD 思想中提出了諸如事件溯源(Event Souring),讀寫隔離(Command Query Responsibility Segregation) 之類的架構模式。
軟體是服務人類,為提高人類生產效率而產生的一種工具, 每一個軟體都服務於某一個特定的領域。例如一個 CRM,它是以管理客戶資料為核心,幫助商家與客戶保持聯繫的工具。
而軟體的實質是電腦中運行的程式碼,如何將抽象的程式碼更準確地映射到人類所關心的領域中,這是軟體開發者一直在探索的話題,不管是函數式編程(FP)還是物件導向程式設計(OOP)也好,都是為了幫助開發者發展出更貼近領域中的軟體模型。
在傳統的軟體開發方法中,我們常常會遇到一系列影響軟體品質的技術以及非技術問題:
開發者熱衷於技術,但缺乏設計和業務思考。開發人員在不完全了解業務需求的情況下,閉門造車,即使功能上線也無人問津。
程式碼輸入而非業務輸入。技術人員對技術實現情有獨鍾,出現殺雞焉用牛刀的情況。
過度重視資料庫。以資料庫設計為中心,而非業務來進行開發,結果往往是,軟體無法適應一直在變動的業務邏輯。
DDD 是一種設計思想,一種以領域(業務)為出發點,以解決軟體建模複雜度為目的設計思想,我們也可以將其理解為一種建模的方法論。
DDD 的設計想法分為戰略和戰術兩部分。
我們可以將策略設計理解為宏觀層面的設計,它的目的包括,分析業務的複雜程度,拆分業務的領域範疇,指導業務整合方式等等 。我們可以將戰術設計理解為微觀層面(程式碼層面)的設計,它為我們在實現業務邏輯提供一系列工具。
語言本是人類溝通的基本工具,然而開發人員習慣了使用技術術語,領域專家(領域專家在此泛指精通業務的專家,例如用戶,客戶等等)對技術術語毫不關心,於是造成了不可避免的溝通問題,一旦溝通出現問題,開發出來的軟體便很難解決領域專家的真正痛點。
通用語言是DDD 思想的基石,它是開發人員和領域專家共同創建一套溝通語言,一套在團隊中流行的,通用的溝通語言,團隊的組員之間可使用通用語言進行無障礙溝通。
這要求開發人員摒棄技術味濃重的技術術語,與領域專家合作,像領域專家一樣關注業務問題,共同挖掘並打磨業務中的術語,創建一套通用語言。
通用語言往往可以直接應用於程式碼中,它可以直接被寫成一個類別或一個類別的方法。
例如在開發一個購物車時,與其使用技術術語:
Cart::create(): 建立一個購物車。
Cart::updateStatus():更新購物車狀態。
Cart::remove():移除購物車。
我們不妨使用更貼近業務的通用語言:
#Cart::init(): 建立一個購物車。
Cart::addItemToCart():新增商品。
Cart::removeItemFromCart():移除商品。
Cart::empty():清空購物車。
在使用後者時,開發人員不用解釋每一個類別方法的意義,領域專家可以直接看懂每一個類別方法的目的。開發人員甚至可以和領域專家坐在一起使用程式碼來打磨業務流程。
在實現了通用語言自由以後,我們需要使用限界上下文來規定每一套通用語言的使用邊界。限界上下文是語意和語境的邊界,在其內的每一個元素都有自己特定的意義,也就是說每一個概念在一個限界上下文中都是獨一無二,不可以出現一詞多義的情況。
我們可以用一個簡單的例子來解釋限界上下文。例如在一個購物車的限界上下文中,我們可以用 User 這個字來代表購買商品的顧客。而在一個註冊系統中,我們可以用 User 一詞指的是帶有使用者名稱和密碼的帳號。雖然詞彙一樣,但是在不同的限界上下文中,它們的意義不同。
我們使用限界上下文和通用語言,對業務進行語言層面的拆分。限界上下文為領域中的每一個元素賦予清晰的概念,開發人員也就不會將情不自禁的在腦海中閃現多個支撐一個元素的概念,避免寫出“大泥球”(big ball of mud)代碼。
如果說限界上下文是對業務進行語言層面拆分的話,那麼子域便是對業務進行商業價值的拆分。每個商業都有自己的關注點,即便是看起來一樣的電商平台,淘寶是開放平台模式,京東是價值鏈整合模式,一個明顯的區別是,淘寶使用第三方物流而京東自建物流體系。
那麼身為開發人員,為何要關心看起來似乎與自己無關的商業模式呢?恰恰相反,只有當我們了解一個商業的結構時,才能發展出一個主次分明的系統來支撐一個商業的快速發展。
子網域便是這樣一個幫助我們分割主次的工具。
有三種類型的子領域:
核心領域(Core Domain):這是系統中需要最大投資的領域,它代表整個商業的核心競爭力。我們需要花費大量資源以及資源來打磨核心域,這關乎一個企業的存亡。例如京東的自建物流系統。
支撐域(Supporting Domain):此領域並非一個企業的核心業務,但是核心域卻離不開它,它可以採用外包客製化方案實現。例如認證上下文,權限上下文。
通用域(Generic Domain):如果已有成熟的解決方案,通用域可以採購現成方案來,如果沒有,也可以採用外包,在通用域上的投資應該是最小的。例如對於淘寶而言,物流便是其通用域。
限界上下文和子域的關係眾說紛紜,有專家提倡1:1,也有專家提倡1:N。個人比較提倡 1:1, 由於受實現領域驅動設計一書作者影響較深。
在一個龐大的系統中,限界上下文之間必定存在一定的依賴關係。如何將一個上下文中的概念對應到另一個上下文?我們使用上下文映射。
以下是幾個上下文映射的關係類型:
合作關係(Partnership)
共享核心(Shared Kernel )
客戶方-供應商開發(Customer-Supplier Development)
遵行者(Conformist)
#防腐層(Anticorruption Layer)
開放主機服務(Open Host Service)
發布語言(Published Language)
另謀他路(SeparateWay)
#大泥球(Big Ball of Mud)
以上都是比較抽象的概念,有時兩個限界上下文之間也可以有多重關係。
以上便是 DDD 策略設計的幾個核心概念。
如何為限界上下文中的概念建立模型,我們將使用的是 DDD 所提供的戰術設計。
首先我們講到的是,實體。
實體是領域中獨立事物的模型,每個實體都有一個唯一的標識符,例如 ID, UUID,Username 等等。大多數情況下,實體是可變的,它的狀態會隨著時間的遷移而改變,不過,一個實體不一定必須可變。
實體的最大的特徵是它的個體性,唯一性。例如在一個簡單的購物車上下文中,訂單(Order) 就是一個實體,ID 是它的標識符,它的狀態可以在提交(placed),確認(confirmed) 以及已退 (refunded) 之間變化。
值物件是領域中用來描述,量化或測量實體的模型。和實體不同,值物件沒有唯一的標識符,兩個對等的值物件是可以替換的。值物件具有不變性(Immutability),一旦創建以後,一個值物件的屬性就定型了,不可更改。
理解價值對象的最直接的方法是,想像我們現實生活中的鈔票,在日常生活中,甲的十塊錢人民幣和乙的十塊錢人民幣是可以對等交換的。
在上文的購物車上下文中,金額(Money)便是一個值對象,金額由貨幣(currency)和數目(amount):
class Money { public $currency; public $amount; function __construct($currency, $amount) { $this->currency = $currency; $this->amount = $amount; } }
使用值對象進行建模,能幫助我們用程式碼更精確地建立領域模型。
聚合是什麼?聚合是上下文中對業務領域更精細的劃分,每一個聚合都保證自己的業務一致性。
那麼什麼是業務不變性?業務不變性表示一個業務規則,該規則在業務領域中不可違背,必須保證其一致性。例如,在進行訂單退款時,退款金額不得超過已付金額。
聚合的組成部分是實體和值對象,有時候也只有實體。為了保護聚合的業務一致性,每個聚合只可以透過某一個實體進行操作,該實體稱為聚合根。
領域事件是透過通用語言分析出來的事件,與常見的事務事件不同的是它與業務息息相關,所以它的命名往往夾帶業務名詞,而不應該與資料庫掛鉤。例如購物車增添商品,對應的領域事件應該是 ProductAddedToCart, 而不是 CartUpdated。
DDD 也提供了應用程式服務(Application Service),領域服務(Domain Service) 等戰術設計,DDD 也提出了文章開頭就提過的事件溯源,六邊形等架構模式,在此我們將不一一介紹。
DDD 的核心是從業務的角度為軟體建立模型,其目的是打造更貼近業務的程式碼,能更直覺的從程式碼理清業務流程。然而實現 DDD 並非一日之舉,它需要不斷的實踐,不斷的打磨。
推薦學習:php影片教學
以上是聊聊php中的DDD的詳細內容。更多資訊請關注PHP中文網其他相關文章!