領域驅動設計(DDD) 是 Eric Evans 提出的一種軟件設計方法和思想,主要解決業務系統的設計和建模。DDD 有大量難以理解的概念,尤其是翻譯的原因,某些詞彙非常生澀,例如:模型、限界上下文、聚合、實體、值對象等。
實際上 DDD 的概念和邏輯本身並不複雜,很多概念和名詞是爲了解決一些特定的問題才引入的,並和麪向對象思想兼容,可以說 DDD 也是面向對象思想中的一個子集。如果遵從奧卡姆剃刀的原則,“如無必要,勿增實體”,我們先把 DDD 這些概念丟開,從一個案例出發,在必要的時候將這些概念引入。
從紙和筆思考 IT 系統的工作邏輯
讓我真正對計算機軟件和建模有了更深入的認識是在一家餐廳喫飯的時候。數年以前,我還在一家創業公司負責餐飲軟件的服務器端的開發工作,因爲工作的原因,外出就餐時常都會對餐廳的點餐系統仔細觀察,以便於改進我們自己產品的設計。
一次偶然的情況,我們就餐的餐廳停電了,所幸是在白天,對我們的就餐並沒有什麼影響。我突然很好奇這家店,在收銀系統無法工作的情況下怎麼讓業務繼續運轉,因此我饒有趣味的等待服務員來接受我們的點單。
故事的發展並沒有超出預期,服務員拿了紙和筆,順利的完成了點餐,並將複寫紙複寫的底單麻溜的撕下來交給了後廚。我這時候纔回過神來。
軟件工程師並沒有創造新的東西,只不過是數字世界的磚瓦工,計算機系統中合乎邏輯的過程,停電後人肉使用紙和筆一樣合乎邏輯。
合乎現實世界的邏輯和和規則,使用鼠標和鍵盤代替紙和筆,就是軟件設計的基本邏輯。如果我們只是關注於對數據庫的增、刪、改、查(CRUD),實際上沒有對業務進行正確的識別,這是導致代碼組織混亂的根本原因。
會計、餐飲、購物、人員管理、倉儲,這些都是各個領域實實在在發生的事情,分析業務邏輯,從中找出固定的模式,抽象成計算機系統中對象並存儲。這就是 DDD 和麪向對象思想中軟件開發的一般過程。
你可能會想,我們平時不就是這樣做的嗎?
現實是,我們往往馬上關注到數據庫的設計上,想當然的設計出一些數據庫表,然後着手於界面、網絡請求、如何操作數據庫上,業務邏輯被封裝到一個叫做 Service 對象上,這個對象不承載任何狀態,業務邏輯通過修改數據庫實現。
[caption id=“attachment_11342” align=“aligncenter” width=“1518”]
(一個樸素的應用系統)
一般來說這種方法也沒有大的問題,甚至工作的很好,Fowler 將這種方法稱作爲事務腳本(Transaction Script)。還有其他的設計模式,將用戶界面、業務邏輯、數據存儲作爲一個“模塊”,可以實現用戶拖拽就可以實現簡單的編程,.net、VF曾經提供過這種設計模式,這種設計模式叫做 SMART UI。
這種模式有一些好處。
- 非常直觀,開發人員學習完編程基礎知識和數據庫 CRUD 操作之後就可以開發
- 效率高,能短時間完成應用開發
- 模塊之間非常獨立
麻煩在於,當業務複雜後,這種模式會帶來一些問題。
雖然最終都是對數據庫的修改,但是中間存在大量的業務邏輯,並沒有得到良好的封裝。客人退菜,並不是將訂單中的菜品移除這麼簡單。需要將訂單的總額重新計算,以及需要通知後廚嘗試撤回正在製作中的菜。
不長眼的新手程序員擅自修改數據片段,整體業務邏輯被破壞。這是因爲並沒有真正的一個 “訂單” 的對象負責執行相關的業務邏輯,Sevice 上的一個方法直接就對數據庫修改了,保持業務邏輯的完整,完全憑程序員對系統的瞭解。
[caption id=“attachment_11344” align=“aligncenter” width=“1366”]
(簡單的增刪改查帶來業務邏輯問題)
我們在各個餐廳交流的時候,發現這並不是一個 IT 系統的問題。某些管理不良餐廳,所有的服務員都可以收銀,而不是專門的收營員收銀;收營員劃掉菜品沒有更新小計,另外的服務員結賬時會發生錯誤。按照程序設計的語言來說,這些餐廳人員職責不清晰,不符合面向對象的一些原則。
我們吸收到這些業務邏輯到 IT 系統中來,並意識到系統中這裏有一些隱藏的模型:
- 訂單
- 菜品
我們決定,抽象出訂單、菜品的對象,菜品不應該被直接修改,而是通過訂單才能修改,無論任何情況,菜品的狀態變化都通過訂單來完成。
複雜系統的狀態被清晰的定義出來了, Service 承擔處理各個應用場景的差異,模型對象處理一致的業務邏輯。
在接觸 Eric Evans 的 DDD 概念之前,我們沒有找到這種開發模式的名字,暫時稱作爲 樸素模型驅動開發。
[caption id=“attachment_11345” align=“aligncenter” width=“1564”]
(最基本的Repository模式)
模型和領域模型
從上面的例子中,模型是能夠表達系統業務邏輯和狀態的對象。
模型是一個非常寬泛的概念,任何東西都可以是模型,我們嘗試給模型下一個定義,並隨後繼續將領域模型的概念外延縮小。
模型,用來反映事物某部分特徵的物件,無論是實物還是虛擬的古人用八個卦象作爲世界運行規律的模型;地圖用線條和顏色作爲地理信息的模型;IT 系統用 E-R 作爲對象或者數據庫表關係的模型;
我們知道要想做好一個可持續維護的 IT 系統,實際上需要對業務進行充分的抽象,找出這些隱藏的模型,並搬到系統中來。如果發生在餐廳的所有事物,都要能在系統中找到對應的對象,那麼這個系統的業務邏輯就非常完備。
現實世界中的業務邏輯,在 IT 系統業務分析時,適合某個行業和領域相關的,所以又叫做領域。
領域,指的特定行業或者場景下的業務邏輯。
DDD 中的模型是指反應 IT 系統的業務邏輯和狀態的對象,是從具體業務(領域)中提取出來的,因此又叫做領域模型。
通過對實際業務出發,而非馬上關注數據庫、程序設計。通過識別出固定的模式,並將這些業務邏輯的承載者抽象到一個模型上。這個模型負責處理業務邏輯,並表達當前的系統狀態。這個過程就是領域驅動設計。
我從這裏面學到了什麼呢?
我們做的計算機系統實際上,是替代了現實世界中的一些操作。按照面向對象設計的話,我們的系統是一個電子餐廳。現實餐廳中的實體,應該對應到我們的系統中去,用於承載業務,例如收銀員、顧客、廚師、餐桌、菜品,這些虛擬的實體表達了系統的狀態,在某種程度上就能指代系統,這就是模型,如果找到了這些元素,就很容易設計出軟件。
後來,如果我什麼業務邏輯想不清楚,我就會把電斷掉,假裝自己是服務員,用紙和筆走一邊業務流程。
分析業務,設計領域模型,編寫代碼。這就是領域驅動設計的基本過程。隨後會介紹,如何設計領域模型,當我們建立了領域模型後,我可以考慮使用領域模型指導開發工作。
- 指導數據庫設計
- 指導模塊分包和代碼設計
- 指導 RESTful API 設計
- 指導事務策略
- 指導權限
- 指導微服務劃分(有必要的情況)
[caption id=“attachment_11347” align=“aligncenter” width=“1190”]
(使用DDD的一般模式)
在我們之前的例子中,收銀員需要負責處理收銀的操作,同時表達這個餐廳有收營員這樣的一個狀態。收營員收到錢並記錄到賬本中,賬本負責處理記錄錢的業務邏輯,同時表達系統中有多少錢的狀態。
分析領域模型時,請把”電“斷掉
我們進行業務系統開發時,大多數人都會認同一個觀點:將業務和模型設計清楚之後,開發起來會容易很多。
但是實際開發過程中,我們既要分析業務,也要處理一些技術細節,例如:如何響應表單提交、如何存儲到數據庫、事務該怎麼處理等。
使用領域驅動設計還有一個好處,我們可以通過隔離這些技術細節,先進行業務邏輯建模,然後再完成技術實現,因爲業務模型已經建立,技術細節無非就是響應用戶操作和持久化模型。
我們可以吧系統複雜的問題分爲兩類:
- 業務複雜度
- 技術複雜度
[caption id=“attachment_11349” align=“aligncenter” width=“1682”]
(分離技術複雜度和業務複雜度)
技術複雜度,軟件設計中和技術實現相關的問題,例如處理用戶輸入,持久化模型,處理網絡通信等。
業務複雜度,軟件設計中和業務邏輯相關的問題,例如爲訂單添加商品,需要計算訂單總價,應用折扣規則等。
當我們分析業務並建模時,過於關注技術實現,會帶來極大的干擾。我學到最實用的思維方法,就是在這個過程把”電“斷掉,技術複雜度中的用戶交互想象成人工交談,持久化想象成用紙和筆記錄。
DDD 還強調,業務建模應該充分的和業務專家在一起,不應該只是實現軟件的工程師自嗨。業務專家是一個虛擬的角色,有可能是一線業務人員、項目經理、或者軟件工程師。
由於和業務專家一起完成建模,因此儘量不要選用非常專業的繪圖的工具和使用技術語言。 DDD 只是一種建模思想,並沒有規定使用的具體工具。我這裏使用 PPT 的線條和形狀,用 E-R 的方式表達領域模型,如果大家都很熟悉 UML 也是可以的。甚至實際工作中,我們大量使用便利貼和白板完成建模工作。
這個建模過程可以是技術人員和業務專家一起討論出來,也可以是使用 ”事件風暴“ 這類工作坊的方式完成。
這個過程非常重要,DDD 把這個過程稱作 協作設計。
通過這個過程,我們得到了領域模型。
[caption id=“attachment_11350” align=“aligncenter” width=“1138”]
(原始領域模型)
上圖使我們通過業務分析得到的一個非常基本的領域模型,我們的點餐系統中,會有座位、訂單、菜品、評價 幾個模型。一個座位可以由多個訂單,每個訂單可以有多個菜品和評價。
同時,菜品也會被不同的訂單使用。
上下文、二義性、統一語言
我們用這個模型開發系統,使用領域模型驅動的方式開發,相對於事務腳本的方式,已經容易和清晰很多了,但還是有一些問題。
有一天,市場告訴我們,這個系統會有一個邏輯問題。就是系統中菜品被刪除,訂單也不能查看。在我們之前的認知裏面,訂單和菜品是一個多對多的關係,菜品都不存在了,這個訂單還有什麼用。
菜品,在這裏存在了致命的二義性!!!這裏的菜品實際上有兩個含義:
- 在訂單中,表達這個消費項的記錄,也就是訂單項。例如,5號桌消費的魚香肉絲一份。
- 在菜品管理中,價格爲30元的魚香肉絲,包含菜單圖片、文字描述,以及折扣信息。
菜品管理中的菜品下架後,不應該產生新的訂單,同時也不應該對訂單中的菜品造成任何影響。
這些問題是因爲,技術專家和業務專家的語言沒有統一, DDD 認識到了這個問題,統一語言是實現良好的領域模型的前提,因此應該 ”大聲的建模“。我在參與這個過程目睹過大量有意義的爭吵,正是這些爭吵讓領域模型變得原來越清晰。
這個過程叫做統一語言。
[caption id=“attachment_11351” align=“aligncenter” width=“1206”]
(領域模型v2)
和現實生活中一樣,產生二義性的原因是因爲我們的對話發生在不同的上下文中,我們在談一個概念必須在確定的上下文中才有意義。在不同的場景下,即使使用的詞彙相同,但是業務邏輯本質都是不同的。想象一下,發生在《武林外傳》中同福客棧的幾段對話。
[caption id=“attachment_11352” align=“aligncenter” width=“728”]
(對話)
這段對話中實際上有三個上下文,這裏的 ”菜“ 這個詞出現了三次,但是實際上業務含義完全不同。
- 大嘴說去買菜,這裏的菜被抽象出來應該是食材採購品,如果掌櫃對這個菜進行管理,應該具有采購者、名稱、採購商家、採購價等。
- 秀才說實習生把賬單中的菜算錯了價格,秀才需要對賬單進行管理,這裏的菜應該指的賬單科目,現實中一般是會計科目。
- 老白說的客人點了一個醬鴨,這裏老白關注的是訂單下面的訂單項,訂單項包含的屬性有價格、數量、小計、折扣等信息。
實際上,還有一個隱藏的模型——上架中商品。掌櫃需要添加菜品到菜單中,客人才能點,這個商品就是我們平時一般概念上的商品。
我們把語言再次統一,得到新的模型。
[caption id=“attachment_11353” align=“aligncenter” width=“1128”]
(領域模型v3)
4個被紅色虛線框起來的區域中,我們都可以使用 ”菜品“ 這個詞彙(儘量不要這麼做),但大家都明確 ”菜品“ 具有不同的含義。這個區域被叫做上下文。當然上下文不只是由二義性決定的,還有可能是完全不相干的概念產生,例如訂單和座位實際概念上並沒有強烈的關聯關係,我們在談座位的時候完全在談別的東西,所以座位也應該是單獨的上下文。
識別上下文的邊界是 DDD 中最難得一部分,同時上下文邊界是由業務變化動態變化的,我們把識別出邊界的上下文叫做限界上下文(Bounded Context)。限界上下文是一個非常有用的工具,限界上下文可以幫助我們識別出業務的邊界,並做適當的拆分。
限界上下文的識別難以有一個明確的準則,上下文的邊界非常模糊,需要有經驗的工程師並充分討論才能得到一個好的設計。同時需要注意,限界上下文的劃分沒有對錯,只有是否合適。跨限界上下文之間模型的關聯有本質的不同,我們用虛線標出,後面會聊到這種區別。
[caption id=“attachment_11354” align=“aligncenter” width=“1074”]
(領域模型v4)
使用上下文之後,帶來另外一個收穫。模型之間本質上沒有多對多關係,如果有,說明存在一個隱含的成員關係,這個關係沒有被充分的分析出來,對後期的開發會造成非常大的困擾。
聚合根、實體、值對象
上面的模型,尤其是解決二義性這個問題之後,已經能在實際開發中很好地使用了。不過還是會有一些問題沒有解決,實際開發中,每種模型的身份可能不太一樣,訂單項必須依賴訂單的存在而存在,如果能在領域模型圖中體現出來就更好了。
舉個例子來說,當我們刪除訂單時候,訂單項應該一起刪除,訂單項的存在必須依賴於訂單的存在。這樣業務邏輯是一致的和完整的,遊離的訂單項對我們來說沒有意義,除非有特殊的業務需求存在。
爲了解決這個問題,對待模型就不再是一視同仁了。我們將那相關性極強的領域模型放到一起考慮,數據的一致性必須解決,同時生命週期也需要保持同步,我們把這個集合叫做聚合。
聚合中需要選擇一個代表負責和全局通信,類似於一個部門的接口人,這樣就能確保數據保持一致。我們把這個模型叫做聚合根。當一個聚合業務足夠簡單時,聚合有可能只有一個模型組成,這個模型就是聚合根,常見的就是配置、日誌相關的。
相對於非聚合根的模型,我們叫做實體。
[caption id=“attachment_11355” align=“aligncenter” width=“1094”]
(領域模型v5)
我們把這個圖完善一下,聚合之間也是用虛線鏈接,爲聚合根標上橙色。識別聚合根需要一些技巧。
- 聚合根本質上也是實體,同屬於領域模型,用於承載業務邏輯和系統狀態。
- 實體的生命週期依附於聚合根,聚合根刪除實體應該也需要被刪除,保持系統一致性,避免遊離的髒數據。
- 聚合根負責和其他聚合通信,因此聚合根往往具有一個全局唯一標識。例如,訂單有訂單 ID 和訂單號,訂單號爲全局業務標識,訂單 ID 爲聚合內關聯使用。聚合外使用訂單號進行關聯應用。
還有一類特殊的模型,這類模型只負責承載多個值的用處。在我們飯店的例子中,如果需要對賬單支持多國貨幣,我們將純數字的 price 字段修爲 Price 類型。
public Clsss Price(){
private String unit;
private BigDecimal value;
public Price(String unit,BigDecimal value){
this.unit = unit;
this.value = value;
}
}
價格這個模型,沒有自己的生命週期,一旦被創建出來就無須修改,因爲修改就改變了這個值本身。所以我們會給這類的對象一個構造方法,然後去除掉所有的 setter 方法。
我們把沒有自己生命週期的模型,僅用來呈現多個字段的值的模型和對象,稱作爲值對象。
值對象一開始不是特別好理解,但是理解之後會讓系統設計非常清晰。”地址“是一個顯著的值對象。當訂單發貨後,地址中的某一個屬性不應該被單獨修改,因爲被修改之後這個”地址“就不再是剛剛那個”地址“,判斷地址是否相同我們會使用它的具體值:省、市、地、街道等。
值對象是相對於實體而言的,對比如下。
[caption id=“attachment_11356” align=“aligncenter” width=“1394”]
(實體和值對象對比)
另外值得一提的是,一個模型被作爲值對象還是實體看待不是一成不變的,某些情況下需要作爲實體設計,但是在另外的條件下卻最好作爲值對象設計。
地址,在一個大型系統充滿了二義性。
- 作爲訂單中的收貨地址時,無需進行管理,只需要表達街道、門牌號等信息,應該作爲值對象設計。爲了避免歧義,可以重新命名爲收貨地址。
- 作爲系統地理位置信息管理的情況中具有自己的生命週期,應該作爲實體設計,並重命名爲系統地址。
- 作爲用戶添加的自定義地址,用戶可以根據 ID 進行管理,應該作爲實體,並重命名爲用戶地址。
我們使用藍色區別實體和聚合根,更新後的模型圖如下:
[caption id=“attachment_11357” align=“aligncenter” width=“1172”]
(領域模型v6)
雖然我們使用 E-R 的方式描述模型和模型之間的關係,但是這個E-R圖使用了顏色、虛線,已經和傳統的 E-R 圖大不相同,把這種圖暫時叫做CE-R圖(Classified Entity Relationship)。DDD沒有規定如何畫圖,你可以使用其他任何畫圖的方法表達領域模型。
使用領域模型指導程序設計
在瞭解到 DDD 之前,到底該用一對多和多對多關係?RESTful API 設計時到底應該選哪一個對象作爲資源地址,評價應該放到訂單路徑下還是單獨出來?訂單刪除相關有多少對象應該納入事務管理?
在沒有領域模型之前,這些大概率憑藉經驗決定,當我們把領域模型設計出來之後,領域模型可以幫助我們做出這些指導。領域模型不只是爲編寫業務邏輯代碼使用,這樣對領域模型來說就太可惜了。
下面是領域模型指導軟件開發的一些方面,具體細節後面會再逐個討論。
指導數據庫設計
通過 CE-R 圖,我們明顯可以設計出數據庫了。不過還有一些細節需要注意。
首先,在之前的認知裏面,多對多關係是非常正常的。但是通過對領域模型的分析後發現,傳統處理多對多關係時,需要額外增加一張關聯表,這張關聯表本質上是一個”關係“的實體沒有被髮掘出來。否則,在實際開發中會造成系統耦合,以及使用 ORM 的時候產生困惑。
菜品和訂單之間是多對多關係嗎?
如果是,菜品和訂單之間耦合了。實際上,菜品的管理處於系統操作的上游,菜品不依賴訂單的任何操作,也就是說訂單的任何變化菜品無需關心。
訂單擁有多個訂單項,每個訂單項從菜品讀入數據並拷貝,或者引用一個菜品的全局 ID (菜品在另外一個聚合)。這樣在設計表結構時訂單和訂單項關聯,訂單項不關聯菜品。訂單項應該從程序讀取菜品信息。看起來多對多的關係,被細緻分析後,變成了一個一對多關係。
[caption id=“attachment_11358” align=“aligncenter” width=“890”]
(數據庫設計)
在使用 ORM 時,良好的領域模型尤其有用。不合適的關聯關係不僅讓 ORM 關聯變得混亂,還會讓 ORM 的性能變差。
使用領域模型建立數據庫的要點:
- 留意多對多關係,並拆解成一對多關係
- 值對象和實體往往爲一對一關係
- 使用 ORM 時,聚合根和實體可以配置爲級聯刪除和更新
- 禁止聚合根之間進行關聯
指導 API 設計
RESTful API 已經變成了主流 API 設計方式,當設計好領域對象後,設計 API 的難度大大降低。
使用聚合根作爲 URI 的根路徑,使用實體作爲子路徑。通過 ID 作爲 Path 參數。
[caption id=“attachment_11359” align=“aligncenter” width=“1562”]
(API設計)
值對象沒有 ID,應該只能依附於某個實體的路徑下做更新操作。
[caption id=“attachment_11361” align=“aligncenter” width=“1562”]
(API設計v2)
另外根據這個關係,處理批量操作的時候應該在實體的上一級完成,例如批量添加訂單的訂單項,可以設計爲:
POST /orders/{orderId}/items-batch
不要設計爲:
POST /orders/{orderId}/items/batch
指導對象設計
在實踐中過程中,像 Java、Typescript具有類型系統的語言,對象很容易被誤用。如果 User 對象既被拿來當做數據庫操作使用,又被拿來當做接口呈現使用,這個類最終變成了上帝類,存在大量可有可無的屬性。
例如用戶註冊時候需要輸入重複密碼,如果在 User 對象中添加 confirmPassword 屬性,存儲時候確並不需要。
因此 DDD 中,數據庫各種對象的使用應該針對不同的場景設計。回到我們上面說的技術複雜度和業務複雜度中來。領域模型解決業務複雜度的問題,領域模型只應該被用作處理業務邏輯,存儲、業務表現都應該和領域模型無關。
[caption id=“attachment_11362” align=“aligncenter” width=“1716”]
(對象設計)
簡單來說,可以把這些 Plain Object 分爲三類:
- DTO,和交互相關或者和後端、第三方服務對接
- Entity,數據庫表映射
- Model,領域模型
另外,在使用領域模型使用上也需要額外注意
- 領域對象儘量使用組合的方式,而不是繼承,現實業務邏輯中繼承這種概念實際上很少。例如菜品的設計,有熱菜、湯菜、涼菜,實際上這裏面並不是菜的繼承,而應該抽象出分類這個模型。
- 不要濫用領域模型,有些業務邏輯,實在找不出一個領域模型很正常,所以 DDD 中存在一個領域服務。例如,生成一個 UUID。有些業務邏輯不持有系統業務狀態,Eric 的書中比喻爲像加油站一樣的業務邏輯。
指導代碼組織
代碼組織,通俗來說就是如何分包。一種狹義的對 DDD 的理解就是指按照 DDD 風格進行代碼組織,雖然 DDD 的內容遠不止於此。
在很長一段時間,我對 DDD 分包策略陷入困惑,後來我明白到,討論 DDD 風格的分包,必須將單體引用和微服務應用分開考慮。
微服務應用在邏輯上和解耦良好的單體應用是一致的。
但是微服務是一種分佈式架構,映射到單體應用中,各個包分佈到不同的服務器中了。我們先以單體應用入手,最後再討論如何將單體應用架構映射到到微服務中。
在事務腳本的模式中,我們一般將代碼分爲三層架構。DDD 特別的抽離出一層叫做 application。這一層是 DDD 的精華,領域模型關心業務邏輯,但是不關心業務場景。
application 用來隔離業務場景,顯得非常重要。舉個例子,用戶被添加到系統中,領域模型處理的是:
- 用戶被添加
- 授予基本權限
- 積分規則創建
- 賬戶創建(三戶模型,客戶、用戶、賬戶往往分開)
- 客戶資料錄入
但是,用戶被添加到系統中由多個應用場景觸發。
- 用戶被邀請註冊
- 用戶自己註冊
- 管理員添加用戶
application 需要隔離應用場景,並組織調配領域服務,才能使得領域服務真正被複用。因此 application 需要承擔事務管理、權限控制、數據校驗和轉換等操作。當領域服務被調用時,應該是純粹業務邏輯,並與場景無關。
如果我們將三層架構和 DDD 架構對比,DDD 架構如右圖所示。
[caption id=“attachment_11363” align=“aligncenter” width=“1726”]
(三層架構對比)
我們將 DDD 的代碼架構展開,可以看到更爲細節的內容。 DDD 代碼實現上需要 Repository、Factory 等概念,但這些是可選的,我們在後面具體講代碼結構的部分再闡述。
[caption id=“attachment_11364” align=“aligncenter” width=“1712”]
(單體DDD架構)
我們再來看,DDD 的單體應用架構映射到微服務架構下會是怎麼樣的。
[caption id=“attachment_11365” align=“aligncenter” width=“1804”]
(單體到微服務)
微服務必須考慮到不再是一個服務,Domain 層被抽離出來作爲 Domain Server 存在,Domain Server 不關心業務場景,因此不需要 application 層。Application Server 需要 Application 層,Domain 層由後端的 Domain Server 提供。
另外補充,一些 DDD 代碼組織的基本邏輯:
- 隔離業務複雜度和技術複雜度
- 使用接口隔離有必要的耦合和依賴倒置
文/ThoughtWorks林寧
更多精彩洞見,請關注微信公衆號:ThoughtWorks洞見