# DDD (~~Deadline Driven Developement~~) (Domain Driven Design) 專注為領域知識設計系統的設計方式 [TOC] ## 名詞定義 ### Domain (領域) - 跟開發的系統相關的知識或使用情境 - 例如:台新 Richart App 開發的 domain 有,戶頭、存款、利息…… - 我們的系統 domain 有,期數、投注、打碼量、派彩…… ### Model (模型) - 用抽象化的方式解釋系統,並解決與領域相關的問題 - 例如:台新的 Richart App 可能會有:Account Model - 我們的系統有,Draw Model - 書基本上都用簡化的 UML 來呈現 Model,但其實 Model 的呈現方式也不一定要 UML,良好的程式碼跟 UML 有同樣的功效(但我們的程式碼通常都不怎麼……) ### Ubiquitous Language (統一戰術語言) - 聽起來好像很厲害,其實簡單來說就是「開發者與非開發者(可能是 PM、可能是業務)」對同一件事,要有同樣的名詞稱呼。 - 好的範例: - Celia: 「期數」的規則是,新增的期數開始時間要晚於最後一期的期數結束時間 - 大家都知道「期數」的定義,在同一個認知下討論事情 - 不好的範例: - Celia: 這邊的報表,要照「遊戲的六大分類」去呈現 - Developer: 六大分類是? - 「遊戲的六大分類」=> GameCategory,我們對 GameCategory 沒有明確的名字, 所以每次講到這裡的時候都要反覆確認很多次 ## 系統的架構 ### Layered Architecture (多層架構) - 就是跟 OSI 概念相近的東西 ( ゚∀゚) - 書中分成四個層 - 使用者介面層 => ex: GameController - 應用層 => ex: GameService - 領域層 => 商業邏輯 - 基礎設施層 => 封裝好資料庫操作的框架 - function 的依賴關係是單向的,由上而下,上層可以操作下層的介面,但下層不能操作上層的介面,除非使用 callback 或觀察者模式 ### Smart UI - 跟 Layered Architecture 完全相反的東西 - 大概就是把所有東西都寫在一起,方便快速暴力 ![](https://i.imgur.com/ImHV40N.png) - 書中表示, Smart UI 也是可以考慮的設計方式,工程師應該要自己分析專案所需,選擇要使用 Layered Architecture 還是 Smart UI - 可以用 Smart UI 的專案特性: - 需要短時間完成的專案 - 專案元件不需要重用 - prototype 等級的專案,需求變化大,會需要快速迭代的專案 - EX: 天瀚點餐系統 ## 用語言建模 - DDD 重視「談話」,透過談話溝通,釐清需求,和規定統一的語言,再來設計模型 - 另外,如果連這個領域的專家,都看不懂你建的模,那你一定建錯了(σ゚∀゚)σ - 書中關於文件美好的想像: - 文件應作為程式碼與口頭交流的補充,程式碼本身能易讀到,大家都懂 - 文件不該重複「程式碼已經明確表達出來的東西」 - 如果文件過時了,並且沒有人覺得有必要去更新,就應該歸檔起來,以免造成混淆 ## Model Driven Design - 原則: - 每個模型會對應到一個領域知識,例如:Draw 對應到「期數」(如果你的模型不能對到某個規格或需求,那他可能不需要存在) - 程式碼用來表達模型 - 程式碼如果修改,代表模型的邏輯被修改,背後必然牽涉到規格的更動(或是你的程式碼有很多蟲,不符合規格,也要修改) - Hand-On Modelers - 他想表達:設計師關在象牙塔裡設計出來的模型,幾乎都是不合現實與規格的,所以要多跟工程師溝通設計模型 ### Entity (Reference Object) - Entity 的屬性都是具有標示性,可以「辨別」這個實體 - 我們關心 Entity 是「誰」 - 舉例:Customer (客戶)Entity 的重構 ![](https://i.imgur.com/TmiRGeX.jpg) ### Value Object - Value Object 的屬性通常是對事物的描述 - 會作為 Entity 的輔助描述 - 我們關心 Value Object 是「什麼」 - 例如: ![](https://i.imgur.com/hSNQ2XR.jpg) - 優點 - 節省資料庫空間 - 共享的 Object 不可變的時候(例如:學校的教室,通常是不可變得,做選課系統的時候, Room 就可以是 Value Object) ### Service - 上面的兩個建模都是「有狀態的」 - 而 Service 則比較像是「一系列程序性的操作」(也就是商業邏輯的部份) - Service 由很多 interface 構成,而 interface 的行為,則由 Entity、Value Object 與規格需求,定義 - Service 的操作都是「無狀態的」(幾乎都是 pure function) ### Module - 毫無反應,就是 Module - Module 之間要低耦合,Module 內部要高內聚(誰不了解這段話的,那個程式設計課重修喔owo) ## 物件的生命周期,與封裝 Model ### Aggregate (骨架) - 其實就是與某個 Entity 有關的 Entity 或 Value Object 們 - 用於在處理 Entity 的修改刪除時,要關注與其相關的其他資料一致性 ![](https://i.imgur.com/qB3CMhQ.jpg) ### Factory (工廠模式) - 毫無反應,就是工廠模式 - 工廠模式可以減少暴露過多的 Model 細節,並且讓複雜的 Model 更便於建立與使用 - 工廠模式關心的是「建立物件」,也就是物件生命周期的開始 ![](https://i.imgur.com/vLhME8F.jpg) ### Repository (嗯? Repo?) - 將資料庫的操作封裝起來,作為 client 端要求物件模型的中間層,更便於 client 存取資料 - 他將商業邏輯的部份,與資料的存取邏輯解耦 - 他可以實踐處理, Aggregate 提到處理 Entity 修改刪除時,關注與其相關資料的一致性 - 他提供了資料存取時的邏輯 - Repository 關心的是「存取與操作物件」,也就是物件生命周期的中後段到結束 ![](https://i.imgur.com/SoGaKym.jpg) ### Factory 與 Repository 並用的方式 ![](https://i.imgur.com/WSmLvCc.jpg) ![](https://i.imgur.com/iLnH35l.jpg) ### Facade (外觀模式) - 毫無反應,就是外觀模式 - 用 Facade (外觀模式) 將 Service 封裝起來,如果 Service 的介面十分複雜,可以將這些複雜的介面操作封裝起來,開發上會更加便利 ![](https://i.imgur.com/Um1Gt56.png) ## 模組設計 (OS: 這個章節超難寫的,要不就簡單到我不知道怎麼下筆,要不就抽象到只可意會,不可言傳) ### Intention revealing interface - 前言 - 如果開發者使用 function 的時候,必須要去研究他的實作,那就失去封裝的意義了 - 如果開發者還必須根據實作細節來推測用途,那這個 function 還可能被誤用 - 結論 - 名字要取好啊owo,不要亂取 - 原則 - 變數名字寫完整 - function 要描述他的效果與目的(關心他「做什麼」),而不是他用什麼方式達到目的(而不是關心他「如何做」) - ex: ![](https://i.imgur.com/AFF4QkP.jpg) ### Side effect free function - 我覺得他想強調「把重要的商業邏輯(領域邏輯)做成 Pure Function」將狀態與與運算分開來,減少副作用 ![](https://i.imgur.com/riP91TW.jpg) ### Assertion - 在寫單元測試的時候,把隱藏起來的定義,表現出來 - ex: - 跑跑腿,騎士接單的時候,地點超過兩公里不能疊單(有沒有這個規定我不知道owo,隨便舉例) - 寫個單元測是暴露這個定義 ### Conceptual contour - 大概是從很多次的重構中,漸漸可以規劃一些底層的領域核心知識(商業邏輯/~~或稱 PM 可能想要什麼~~),當有新需求的時候,可以更快速的應變 ### Standalone class - 原則:減少依賴 ### Closure of operation - 定義:操作的時候,是操作自己的類別 - ex: function (a: Apple, b: Apple) => Apple - 目的:減少依賴 ## 系統設計 ### Context - Bounded Context:一個系統會有很多子系統,每個子系統都會有單一負責的事,界定清楚子系統要做的事,避免誤用子系統 - Continuous integration - 背景 - 當很多開發者在同一個子系統下工作 - 問題 - A 開發者沒有了解 B 開發者的 function 到底是幹嘛的,就修改他,導致他失去作用 - A、B 都開發了重複的 function,因為他們不知道已經有現成的 function 可以用 - 結論 - 當子系統也越來越大的時候,就需要分裂成更小的系統 - Context Map:兩個子系統之間的關係地圖 ### Shared kernel - 將多個系統都用到的基礎 function 抽成共用的函式庫 - 並且規定: - 在沒有跟,用到這個函式庫的團隊協商之前,不可以擅自修改 - 每次整合函式庫或修改的時候,都要執行一次測試,每個用到的團隊都要執行並通過測試 ### Customer/Supplier development team - A 套件(下游)的開發,相依於 B 套件(上游)需要先開發好 - 更明顯的例子是:前後端的關係,後端的 API 是上游,而前端是下游 - 重點是上游必須要有自動化測試,確保修改程式的時候,不會改壞東西,影響下游 ### Conformist - 背景 - 上游開發完後,倒閉或跑路了 - 總之可能因為各種原因,所以上游不在了,新的修改沒有人做 - 但上游的程式碼品質沒有太差 - 做法 - 因為下游程式碼很大一部份受限於上游,所以一個辦法是順著上游的程式碼自己再做修改 - 或者是寫一層轉換層銜接 ### Anticorruption layer - 背景 - 像剛剛說的,上游跑了 - 或是跟一個陳舊的系統銜接 - 作法 - 與新系統銜接的地方,用 service 包起來,有幾個地方要接,就包幾個 service,每一個 service 都對應一個 adapter,用來轉換資料 - 與舊系統或其他系統銜接的地方,用 Facade 包起來,只對外開出一個介面 ![](https://i.imgur.com/KwoCN6k.jpg) ### Separate way - 背景 - 整合的代價很高,而獲益卻很小的時候 - 需要快速完成一個專案(Side project)的時候 - 作法 - 就...不要整合(我不知道這個章節寫出來做什麼的,混字數嗎) - 減少跟其他系統共用的資料,將 side project 分開設計 ### Open host service - 背景 - 有一個很公用的子系統,會跟很多模組銜接 - 每個模組如果都要寫一個轉換層,會有很多重複的工 - 作法 - 定義一個 protocol,將子系統作為一組 service,有新需求的時候,就擴增這個 protocol ### Published language - 就是資料傳輸或轉換時,一個公用的資料型態 - ex: XML、JSON ## 開發方向與重構 ### Core domain - 區分系統中,最核心、最重要的模組 - ex: 彩票模組 - 將核心模組與其他輔助用模組區分開來 - 將重心投入在核心模組中,重構或最佳化它,並投入最好的人力進去 - 如果你的系統的核心模組並沒有特殊需求,那可以考慮乾脆直接買現成的系統 ### Domain vision statement - 願景說明,就是 Core domain 的核心價值是什麼,通常用一頁紙寫完就好,與核心價值無關的東西就不用寫了 - 他是一個指標,可以讓團隊時時刻刻寶,在對的方向 ### Highlighted core - 有了願景之後,我們可以拿願景與設計好的系統去做比較,寫成一份低於十頁的文件 - 雖然文件會有風險: - 沒有人維護 - 沒有人看 - 文件太雜 - 書中是鼓勵大家要儘量精練文件,並且從精練文件的過程中,更清楚 Core domain 與 Domain vision statement 的方向 - 而文件本身也可以作為新進人員的教材 ### Generic subdomain - 其次區分一些重要、泛用的模組,但並不是我們核心的業務 - ex: 財務相關模組 - 將他們獨立放到單獨的 Module 中 - 他們的開發順序也比核心業務還要後面 - 而這樣的模組也可以考慮以下方式替代 - 買現成的方案,或用開源的方案 - 抄別人設計好的模組(他沒說哪裡找這種東西耶,可能是 GitHub 吧) - 外包實作模組 - 自己做owo - 通用不等於可重用 - 設計通用的模組時,雖然這類型的模組裏面的程式碼或是模型可能可以被重用(resuse),但我們的目的不是要開發一個萬能的模組,所以我們不需要總是考慮模組的重用性,只要把模組設計成大部份狀況下通用就好 ### Cohesive mechanism - 前面有提到「Intention revealing interface」的一個重點是關心模組「做什麼」,而不是他「如何做」,而我們可以把「如何做」(也就是演算法的部份)封裝成另一個模組,就可以讓起他開發者不用多花心力研究他的實作 ### Segerated core - 如果 Core domain 變得太大,在開發上核心的部份會變得不太好看出來 - 這個時候可以透過「Highlighted core」提到的文件,邊歸納哪些是重要的功能或模型,邊重構或拆表 - 將比較不是核心的部份,拆成新的模組 ### Abstract core - 即使做完 Segerated Core 後,還是可能有,三四個 Module 彼此依賴,他們的關係從圖表來看,可能也還是很混亂 - 書中建議我們可以,把幾個大方向的東西用抽象類別作為代表,這些抽象類別放在獨立的 Module 可以表現彼此的關係,而實作細節就放在他原本的 Module 中 - 就是又多出一個用以表現關係的 Module ## 處理大型的結構 ### Evolving order - 背景 - 沒有任何規則的系統設計會造成「無法理解整個系統架構和難以維護」 - 但若在專案早期過度設計,訂下很多規定,又會讓專案開發變得綁手綁腳的 - 作法 - 開發的規定必須要隨著專案一起演進 - 所以隨著時間轉變,未來開發上的規定,可能跟過去截然不同 - 另外我們在約束開發者的規定,儘量精簡,不要為了過於理想的理論,而施加不合適的規則 ### System metaphor - 背景 - 軟體設計有的時候太抽象,會澀難懂 - 作法 - 書中建議我們可以用一些比喻來描述系統 ### Responsibility layer - 系統發展一陣子,每個模組都有各自的職責,這時可以根據各個職責的相似度分層 - 這種分層方式很容易將模組區分出來,也可以分出哪種職責是上層,哪種職責是下層,下層的模組不能取用上層的模組 ### Knowledge level - 用 Type 的模型,來讓讓隱藏的概念轉變成顯示概念 ![](https://i.imgur.com/IEhXypT.jpg) ### Pluggable component framework - 從 interface 中分出一個 abstract core 並建立一個框架,讓框架透過 abstract core,可以自由替換實作介面 - 透過 pluggable component framework 可以讓系統自由與其他系統整合 - 但同時,也會限制 core domain 的發展,因為修改 abstract core 的成本更大 ## 制定戰略的要點 - 決策必須傳達給整個團隊 - 決策必須收集回饋意見 - 計劃必須允許演變 - 架構團隊不必把所有最厲害的人都吸收進來 - 戰略儘量簡潔 ## 結論 - 如果不是這本書翻譯的不好,就是 DDD 是一門玄學