Try   HackMD

DDD (Deadline Driven Developement) (Domain Driven Design)

專注為領域知識設計系統的設計方式

名詞定義

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 完全相反的東西
  • 大概就是把所有東西都寫在一起,方便快速暴力
    Image Not Showing Possible Reasons
    • The image file may be corrupted
    • The server hosting the image is unavailable
    • The image path is incorrect
    • The image format is not supported
    Learn More →
  • 書中表示, 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 的重構
    Image Not Showing Possible Reasons
    • The image file may be corrupted
    • The server hosting the image is unavailable
    • The image path is incorrect
    • The image format is not supported
    Learn More →

Value Object

  • Value Object 的屬性通常是對事物的描述
  • 會作為 Entity 的輔助描述
  • 我們關心 Value Object 是「什麼」
  • 例如:
    Image Not Showing Possible Reasons
    • The image file may be corrupted
    • The server hosting the image is unavailable
    • The image path is incorrect
    • The image format is not supported
    Learn More →
  • 優點
    • 節省資料庫空間
    • 共享的 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 的修改刪除時,要關注與其相關的其他資料一致性
    Image Not Showing Possible Reasons
    • The image file may be corrupted
    • The server hosting the image is unavailable
    • The image path is incorrect
    • The image format is not supported
    Learn More →

Factory (工廠模式)

  • 毫無反應,就是工廠模式
  • 工廠模式可以減少暴露過多的 Model 細節,並且讓複雜的 Model 更便於建立與使用
  • 工廠模式關心的是「建立物件」,也就是物件生命周期的開始
    Image Not Showing Possible Reasons
    • The image file may be corrupted
    • The server hosting the image is unavailable
    • The image path is incorrect
    • The image format is not supported
    Learn More →

Repository (嗯? Repo?)

  • 將資料庫的操作封裝起來,作為 client 端要求物件模型的中間層,更便於 client 存取資料
  • 他將商業邏輯的部份,與資料的存取邏輯解耦
  • 他可以實踐處理, Aggregate 提到處理 Entity 修改刪除時,關注與其相關資料的一致性
  • 他提供了資料存取時的邏輯
  • Repository 關心的是「存取與操作物件」,也就是物件生命周期的中後段到結束

Factory 與 Repository 並用的方式

Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →

Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →

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 是一門玄學