# 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 完全相反的東西 - 大概就是把所有東西都寫在一起,方便快速暴力  - 書中表示, 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 的重構  ### Value Object - Value Object 的屬性通常是對事物的描述 - 會作為 Entity 的輔助描述 - 我們關心 Value Object 是「什麼」 - 例如:  - 優點 - 節省資料庫空間 - 共享的 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 的修改刪除時,要關注與其相關的其他資料一致性  ### Factory (工廠模式) - 毫無反應,就是工廠模式 - 工廠模式可以減少暴露過多的 Model 細節,並且讓複雜的 Model 更便於建立與使用 - 工廠模式關心的是「建立物件」,也就是物件生命周期的開始  ### Repository (嗯? Repo?) - 將資料庫的操作封裝起來,作為 client 端要求物件模型的中間層,更便於 client 存取資料 - 他將商業邏輯的部份,與資料的存取邏輯解耦 - 他可以實踐處理, Aggregate 提到處理 Entity 修改刪除時,關注與其相關資料的一致性 - 他提供了資料存取時的邏輯 - Repository 關心的是「存取與操作物件」,也就是物件生命周期的中後段到結束  ### Factory 與 Repository 並用的方式   ### Facade (外觀模式) - 毫無反應,就是外觀模式 - 用 Facade (外觀模式) 將 Service 封裝起來,如果 Service 的介面十分複雜,可以將這些複雜的介面操作封裝起來,開發上會更加便利  ## 模組設計 (OS: 這個章節超難寫的,要不就簡單到我不知道怎麼下筆,要不就抽象到只可意會,不可言傳) ### Intention revealing interface - 前言 - 如果開發者使用 function 的時候,必須要去研究他的實作,那就失去封裝的意義了 - 如果開發者還必須根據實作細節來推測用途,那這個 function 還可能被誤用 - 結論 - 名字要取好啊owo,不要亂取 - 原則 - 變數名字寫完整 - function 要描述他的效果與目的(關心他「做什麼」),而不是他用什麼方式達到目的(而不是關心他「如何做」) - ex:  ### Side effect free function - 我覺得他想強調「把重要的商業邏輯(領域邏輯)做成 Pure Function」將狀態與與運算分開來,減少副作用  ### 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 包起來,只對外開出一個介面  ### 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 的模型,來讓讓隱藏的概念轉變成顯示概念  ### Pluggable component framework - 從 interface 中分出一個 abstract core 並建立一個框架,讓框架透過 abstract core,可以自由替換實作介面 - 透過 pluggable component framework 可以讓系統自由與其他系統整合 - 但同時,也會限制 core domain 的發展,因為修改 abstract core 的成本更大 ## 制定戰略的要點 - 決策必須傳達給整個團隊 - 決策必須收集回饋意見 - 計劃必須允許演變 - 架構團隊不必把所有最厲害的人都吸收進來 - 戰略儘量簡潔 ## 結論 - 如果不是這本書翻譯的不好,就是 DDD 是一門玄學
×
Sign in
Email
Password
Forgot password
or
Sign in via Google
Sign in via Facebook
Sign in via X(Twitter)
Sign in via GitHub
Sign in via Dropbox
Sign in with Wallet
Wallet (
)
Connect another wallet
Continue with a different method
New to HackMD?
Sign up
By signing in, you agree to our
terms of service
.