
前言
戰術設計
戰略設計為我們提供一種高層視野來審視我們的軟件系統,主要包括領域/子域、通用語言、限界上下文和架構風格等概念,
而戰術設計則將戰略設計進行具體化和細節化,它主要關注的是技術層面的實施,也是對程序員來得最實在的地方。
戰術設計的目的是保證戰略的實現。在DDD中,代碼就是設計本身,你不再需要那些繁文縟節的並且永遠也無法得到實時更新的設計文檔。
警惕貧血對象,要創建行為飽滿的領域對象並不難,我們需要轉變一下思維,將領域對象當做是服務的提供方,而不是數據容器,多思考一個領域對象能夠提供哪些行為,而不是數據。
實體
一個實體模型就是一個獨立的事物,采用充血模型,具有業務屬性和業務行為。每個實體都擁有一個唯一的標識符,可以將它的個性化和所有其他類型相同或者不同的實體區分開。許多時候,實體是可變的,它的狀態會隨着時間發生變化。
對一個實體進行多次修改,修改后的數據和原來可能會不大相同,但它們依然是同一個實體,因為唯一標識沒變。
值對象
一個值對象,是對一個不變的概念整體所建立的模型,沒有一個唯一的標識符。在這個模型中,值就真的只有一個值。和實體不一樣,它沒有唯一標識符,而是由值類型封裝的屬性對比來決定相等性。此外,一個值對象不是實物,而是常常用來描述、量化或者測量一個實體。
一個典型的值對象是地址值對象,比如下單中,有下單人、商品信息、優惠信息、還有收貨地址信息,如果地址信息用省、市、區等屬性表示,會有些零碎,拿出來構成一個“地址”屬性,會更合適一些,這個多個值的集合就是值對象了。
聚合
在DDD中,實體和值對象是很基礎的領域對象,實體跟值對象都只是個體化的對象,它們的行為表現出來的是個體能力。聚合用來確保這些領域對象在實現共同的業務邏輯時,能保證數據的一致性。
聚合是由業務和邏輯緊密關聯的實體和值對象組合而成的。聚合是數據修改和持久化的基本單元。
聚合設計的四條基本規則
- 在聚合邊界內保護業務規則不變性
- 聚合要設計得小巧
- 只能通過標識符引用其他聚合
- 使用最終一致性更新其他聚合
聚合根
每個聚合都有一個根實體,叫做聚合根,外界只能通過聚合根跟聚合通信。聚合根的主要目的是為了避免由於復雜數據模型缺少統一的業務規則控制,而導致聚合、實體之間數據不一致的問題。
如果把聚合比作一個團隊,那么聚合根就是團隊的leader,其他團隊的需求都需要跟該團隊的leader商量后才能開始動工。
領域服務
當某個操作不適合放在聚合和值對象上時,最好的方式便是使用領域服務了。
可以使用領域服務的地方,過度使用領域服務將導致貧血領域模型。
- 執行一個顯著的業務操作過程
- 對領域對象進行轉換
- 已多個領域對象作為輸入進行計算,結果產生一個值對象
領域服務不需要定義接口,直接定義實現即可
- 如果接口有不同的實現,那么需要考慮領域中是否存在特定的功能行為
- 如果我們采用了依賴注入或者工廠,即便接口和實現類是合並在一起的,我們依然能達到這樣的目的。依賴倒置容器(例如 Spring)將完成服務實例的注入工作,由於客戶端並不負責服務的實例化,它並不知道接口和實現類是分開的還是合並在一起的。
領域服務與實體方法的區別
- 實體方法完成單一實體自身的業務邏輯,相對簡單的原子業務邏輯
- 領域服務則是多個實體組合出的相對服務的業務邏輯
倉儲
倉儲用於保存和獲取聚合對象,應該將倉儲看作一個對象的集合,而不是數據庫的CRUD,更不是一張表一個倉儲。
需要做到讓用戶無感知的,以為就在內存中使用一個集合一樣。
倉儲的出現就是為了讓大家聚焦領域模型,不要聚焦表結構。
領域事件
領域事件是一條記錄,記錄着在限界上下文中發生的對業務產生重要影響的事情,經常用於保證兩個聚合之間的一致性。
對於領域事件的命名,必須體現出模型的通用語言,這些名詞是連接模型之間的橋梁,對發生的事情進行充分的溝通至關重要。
領域事件類型名稱應該是對過去發生事情的陳述,即動詞的過去式,如OrderCreated。
參考資料
- 《領域驅動設計——軟件核心復雜性應對之道》
- 《實現領域驅動設計》
- 《領域驅動設計精粹》
