# Ch.01 Introducing Domain-Driven Design 筆記 ## Overview 將以 DDD 手法探討如何分析現實世界的 `訂單系統` 。 ## Garbage in Garbage out 有人認為 developer 只是寫 code。 事實上 developer 是以 software 解決問題,coding 只是其中一部分,尚包含 Communition 與 Good Design,這也是本書要強調的部份。 假如你也承認 software development 是一種 pipeline,以 `需求` 為 input,以 `解決問題` 為 output,則 `garbage in, garbage out` 法則也適用,如果 input 為 `不明確需求` 或 `不好的設計`,無論再好的 code 而無法產生預期 output。 本書 part 1:`Understanding the Domain` 就是為了減少 `garbage in`,以 DDD 為方法更清楚的 Communication 與 Good Design (Shared Domain Knowledge)。 本章節將以實際範例討論 DDD 原則並實際分析 domain。 DDD 並不是萬靈丹,也不適用於如 system software、games ... 等領域,特別適合 business 或 enterprise software,也就是 developer 必須與其他非 IT 領域的團隊一起合作,這是本書所要討論的。 ## The Importance of a Shared Domain Model  在解決問題之前必須先了解問題是什麼,但可惜的是 Development Team 認知與 Domain Expert 認知不一定相同,因此可能產生 `garbage in`。 一些 software development process 使用 specification 或 requirement document 嘗試以 Development Team 角度捕捉所有問題,由小孩子玩的 `傳聲筒` 遊戲經驗可得知,訊息經過一連串人的理解與傳遞之後,訊息常常會不斷變形,因此造就彼此認知落差。 > * Developer Team:Developer、UX/UI designer、Tester > * Domain Expert:真正了解 Domain 的專家  Agile 採用方式是 Domain 與 Development Team 直接溝通,避免中繼人員的理解與傳遞,如此可有效避免 `訊息變形`。 但這樣仍有些缺點: * Developer Team 必須將 Domain expert 心中的 Mental Model 翻譯成 Developer 所理解的 Domain Model 用於 code 中,這仍可能造成 `訊息變形` 或忽略重要細節 * 由於 Developer 的 Domain Model 與 Domain Expert 的 Mental Model 仍有所差異,將來接手維護的 Developer 由於沒有直接與 Domain Expert 溝通過,只看到目前 code 中的 Domain Model,因此可能對真正 Domain 造成誤解  DDD 則希望 Domain Expert、Development Team 、other stakeholder 都使用相同的 Mental Model 為 Domain Model,code 只是用來處理此 Domain Model 而已,而非 Developer 根據自己的理解重新建立 Domain Model。 使用共同的 Domain Model 有幾個優點: * **更短的 time to market**:避免 `訊息變形` 造成誤解而能更快速解決問題 * **更多商業價值**:避免只是做出 developer 想要的產品而非 customer 想要的產品而走偏 * **更少浪費**:更清楚的需求避免 `garbage in` 可減少將來 rework 時間,也更能儘早發現哪些才是 Domain Expert 認為重要的部分,讓 Development Team 將資源放在對的地方 * **更易維護**:由於 code 中的 Domain Model 與 Domain Expert 一致,也讓將來接手維護的 developer 更容易與 Domain 結合上手 > **The Insanely Effective Delivery Machine** > > Dan North 是一個著名的 Behavior-Driven Development 傳教士,他曾在 `Accelerating Agile` 分享在幾周內開快速開發出 trading system 經驗,他認爲成功關鍵在於 Shared Mental Model,它讓每個 developer 直接與 Domain Expert 溝通,並使用相同的 Domain Model,這使的彼此溝通非常有效率,且最後 developer 也成為 Domain Expert,並能快速建立系統 DDD 歸納出幾個 guideline 協助我們建立 shared domain model: * `將焦點放在 Business Event 與 Workflow 而非 Data Structure` * `將 Problem Domain 切割成更小的 Subdomain` * `根據 Subdomain 建立 Domain Model` * `為 Developer Team 與 Domain Expert 間建立 Ubiquitous Language` ## Understanding the Domain Through Business Events 根據 DDD 第一條 guideline: > 將焦點放在 Business Event 與 Workflow 而非 Data Structure 企業並不是只有 data,而是在於將 data 經過處理而獲利,這是一個 transformation。 到底是什麼促使 transformation 開始呢 ? 它常常是外部 trigger: * 有 email 進來 * 有電話進來 * 每天 10:00 要做 * 若沒有任何訂單就做其他事情 一開始必須將這些 Domain Event 都抓出來。Domain Event 是建立 Domain Model 的起手式。 > 如 `當收到訂單時` 就是一個 `訂單系統` 中觸發 `處理訂單` process 的 Domain Event。 Domain Events 會以 `英文過去式` 表示,表示已經發生且無法改變的事實。 ### Using Event Storming to Discover the Domain * 將所有相關人都加入討論 (Domain Expert、Development Team、other stakeholder) * 牆壁必須有 `白報紙` 或 `白板` 可貼 `便利貼` 及直接書寫 * 所有人將 Business Event 與其觸發的 Business Workflow 寫在便利貼並貼在牆 * 依 `時間軸` 先後調整便利貼順序 * 目的將所有的 Business Event 與 Business Workflow 都找出來 ### Discovering the Domain: An Order-Taking System 本書將以 `訂單系統` 為實例探討 DDD、Domain Model 與 FP 實作。 Max (總經理): > 我們是個小公司,目前訂單作業都是人工與書面處理,由於公司業務擴展迅速,目前訂單處理已經非人工所能負荷,我們希望能全面數位化處理,因此希望有個 seft-service 網站,顧客可自行處理某些訂單業務,如 `下訂單`,`查詢訂單狀態` ... 等 You: > 請開始用便利貼將 Business Event 貼在牆上: Ollie (訂單部): > 我是 `訂單部` 的 Ollie,我們會處理 `訂單` 與 `報價單` You: > 什麼事件會引發這個流程呢 ? Ollie (訂單部): > 當顧客藉由 email 將 `表格` 傳給我們時 You: > 聽起來像是 `order form received` event 與 `quote form received` event 是嗎 ? Ollie (訂單部): > 是的,讓我們將這些 event 用便利貼貼在牆上 Sam (物流部): > 我是 `物流部` 的 Sam,當 `訂單` 成立時,我們將 `接單出貨` You: > 你何時會知道該 `接單出貨` 呢 ? Sam (物流部): > 當我們收到 `訂單部` 的表格時 You: > 你會如何稱呼該 event 呢 ? Sam (物流部): > 稱為 `order avaliable` 如何 ? Ollie (訂單部): > 我們稱即將出貨的訂單為 `placed order`,我們可對該名詞統一嗎 ? Sam (物流部): > 所以稱為 `order placed` event 大家同意吧 ?  經過一段時間討論,牆上出現以上便利貼。 由以上對話,我們可發現藉由 Event Storm 可幫助我們釐清以下幾件事情: * **凝聚各部門共識** 除了探索出 Business Event 外,因為參與者都一起看到牆上的便利貼,使得大家對整個系統凝聚共識,讓每個部門了解其他部門的 Domain,也澄清了對其他部分 Domain 的誤解,甚至 Development Team 也可發現藉由 IT 技術對整個 Business 有新的解決問題方法。 * **增進各部門了解** 多數人容易以自己的部門來思考事情,而忘記了其他部門也可能相關, Black (帳務部): > 我是 `帳務部` 的 Blake,我們也需要完整的訂單資料,藉此我們才能對顧客收費,因此我們也需要參與 `order placed` event * **找出未發現需求** 當所有 Business Event 都根據時間軸攤在牆上時,很容找出未發現需求。 Max (總經理): > Ollie (訂單部),當訂單成立時,你有告知顧客嗎 ? 我在牆上沒看到便利貼 Ollie (訂單部): > 是的,我忘記了,當訂單成立時,我們會發 email 給顧客通知訂單已經成立並準備出貨,這是另一個 event:`order acknowledgement sent to customer` event 假如問題無法馬上有明確的答案,應該將 `問題本身` 以便利貼貼到牆上,將來另外討論。 事實上很多需求一開始都很模糊,與其一開使就貿然進行開發而事後一再 rework,不如先把問題紀錄下來,另外找時間將問題釐清。 * **各部門以 Event 結合**  當所有 Event 與 Workflow 都貼在牆上時,可進一步將之以 `部門分類`,如此可發現當 `訂單部` 訂單成立時,其 output 發起 `order placed` event 正是 `物流部` 與 `帳務部` 的 input。 在此階段並不討論實作細節,如該使用 message quene 或 database ... 等,這裡只在乎 Domain 中各部門的關係。 * **察覺出報表需求** 在了解 Domain 時很容易發現 rocess 與 transaction,但別忘了 reporting 也一樣重要,Event Storm 階段也可一起討論出 reporting 與 UI 部分。 ### Expanding the Events to the Edges 隨著 Event Storm 挖掘出更多的 Business Event 且依時間軸排序後,你應該不斷問自己是否最左側與最右側還存在其他 Event,試圖找出 system 的 boundary。 You: > Ollie (訂單部),什麼會引起 `order form received` event 呢 ? Ollie (訂單部): > 每天早上我們會開啟 email 查看是否有新的 `訂單` 或 `報價單` You: > 所以聽起來我們還需要 `mail received` event 嗎 ? 同理我們也可找出 system 最右側的 boundary。 You: > Sam (物流部),是否存在任何 event 在 `Order shipped` event 之後呢 ? Sam (物流部): > 如果訂單是 `貨到簽收`,當顧客收到貨後,我們會從 `客服部` 收到通知,所以應該加上 `shipment received by customer` event  * **Scenario**:User 或 department 想要達成的目標,但不包含實際細節,Agile 稱為 **story**。 * **Use Case**:Scenario 的實際細節。 * **Busines Process**:企業想要達成的目標。 * **Workflow**:Business Process 的實際細節。 ### Documenting Commands 當使用 Event Storm 在牆上找到眾多 Business Event 之後,下一步的疑問就是:如何使這些 Event 發生呢 ? 如 `顧客發 email` 的動作才會使 `order form received` event 發生,在 DDD 稱為 **Command**。 >不要跟 OOP 的 Command Pattern 搞混 當 Command 成功執行,它會引起 Workflow,並且發起 Business Event。 * `Make X happen` Command: 執行 `make X happen` workflow,並發起 `X happend` event。 * `Send an Order form to Widget Inc.` 執行 `send the order` workflow,並發起 `Order form sent` event。 * `Place an order` command: 執行 `place an order` workflow,並發起 `Order placed` event * `Send a shipment to customer ABC` command: 執行 `send a shipment` workflow,並發起 `Shipment send` event  實務上 Business Process 會以如上的 Model 呈現: * 由 Event 觸發 Command * 由 Command 執行 Workflow * 由 Workflow 觸發更多 Event > 我們可發現這種 Model 剛好跟 FP 的 Function Pipeline 相契合。  根據以上方法呈現我們的 `訂單系統`。 目前我們先假設所有 Command 都成功執行,將在 `Ch.10 Implementation: Working with Errors` 討論如何處理 Command 執行失敗。 此外,並非所有 Event 都是由 Command 觸發,有些 Event 可能由 scheduler 或 monitor system 觸發。 ## Partioning the Domain into Subdomains 根據 DDD 第二條 guideline: > 將 Problem Domain 切割成更小的 Subdomain 雖然目前已經搜集了眾多 Event 、 Command 與 Business Process,但對於整個系統的了解仍是混亂的,在寫 code 之前仍必須做更進一步整理。 也就是將眾多 Event、Command 與 Business Process 的 `訂單系統` 切割成更小的子系統:如 `訂單子系統`、`物流子系統`、`帳務子系統` ... 等,誠如企業中已經存在各部門一樣,我們可稱此子系統為 Domain。 Domain 在 DDD 中定義為 `有特定知識的領域`,白話文就是 `存在 Domain Expert 的領域`。  以我們都熟習的 Domain 而言,`Web Programming` 算 `General Programming` 的 Subdomain,而 `JavaScript Programming` 又是 `Web Programming` 的 Subdomain。 除此之外,Domain 也能 overlap,如 CSS 橫跨 `Web Programming` 與 `Web Design`。 理論上,我們都希望切出來的 Subdomain 能明確而互不相干,但現實世界並沒有想像中單純。  現實中的 Domain 會有些 overlap,`訂單部` 必須稍微了解 `物流部` 與 `帳務部` 流程,而 `物流部` 也對 `訂單部` 與 `帳務部` 的業務有所認識。 誠如之前 Dan North 的例子,developer 也該成為半個 Domain Expert,整個開發效率才會大幅提升。 ## Creating a Solution Using Bounded Contexts 根據 DDD 第三條 guideline: > 根據 Subdomain 建立 Domain Model 了解 Problem 與 Domain 並不代表能建立 Solution,畢竟資訊太多了,我們必須找出能解決問題的 Event、Command 與 Process。  首先我們必須將 Problem Space 與 Solution Space 分開,在 Problem Space 中從眾多 Event、Command、Process 中取出與解決問題的資訊在 Solution Space 中建立 Domain Model。 在 Solution Space 中的 Domain 與 Subdomain 在 DDD 稱為 **Bounded Context**,在每個 Bounded Context 中有其各自 Domain Model。 > 之所以 DDD 不稱為 Subsystem,因為其重點在於其 Context 與 Boundary ### Context 在相同 Context 中有其專有名詞與行話,這使的參與其中的人使用共同語言溝通,因為在現實世界中,同一個單字在不同領域常有不同意義。 ### Boundary 在現實世界中,Domain 很難無 overlap,但在 software 開發上,我們會希望各系統能避免彼此耦合、沒有 side effect 而能獨立演進,為了是減少複雜度與方便日後維護,因此我們傾向建立彼此 `沒有 overlap` 的 Domain Model,僅管此 Model 與現實世界的 Domain 有些許出入。 > Bounded Context 與 Subdomain 還是會有些差異,主要是為了更適合 software 開發 在 Problem Space 的 Domain 與 Solution Space 的 Bounded Context 並不需要一對一對應,實務上常常一個 Domain 分裂成多個 Bounded Context,或者多個 Domain 合併成單一 Bounded Context。 假如公司已經分別有 `訂單系統` 與 `財務系統`,假如你必須整合這兩個系統,僅管現實上是不同 Domain,但設計上會使用相同 Bounded Context。 無論怎麼切割 Domain,重點是每個 Bounded Context 有其明確的 responsibility,因為在實作 Domain Model 時,每個 Bounded Context 實作方式可能是獨立 DLL、可能是獨立 Service,或者是獨立 namespace,雖然實作方式並不是現階段重點,但大方向是相同的。 ### Getting Context Right 定義 Bounded Context 聽起來很直覺,但實作上卻很 tricky,事實上 DDD 最大的挑戰就是 `如何決定正確 boundary ?`,這已經是藝術等級,而非科學問題,以下是 DDD 所建議的 guideline: * `傾聽 Domain Expert 建議`:假如該 Event、Command 與 Process 都使用相同行話且關注相同問題, 他們大致上是在相同 Bounded Context 內 * `觀察企業的組織劃分`:畢竟因為業務不同才會畫分出如此的組織結構,他們大致上是在相同 Bounded Context 內 * `別對 Boundary 有婦人之仁`:由於問題變化太快,可能會想將 boundary 模糊化面對,但 boundary 太大或太模糊等於沒有 boundary,俗語說:`Good fences make good neighbors`。 * `為了將來獨立發展`:硬將不相關的 Domain 取其最大公約數放在相同 Bound Context 下將限制未來發展,應將其各自放在不同 Bounded Context 下 * `為了更順暢的 Workflow`:假如該 Workflow 會橫跨多個 Bounded Context,但因為 Cotext 的種種限制而使 Workflow 不順,可考慮將多個 Bounded Context 合而為一,雖然整個設計可能看起來比較醜,但畢竟 DDD 重視的是解決問題與客戶價值,而非設計出漂亮的架構 沒有任何架構是一成不變的,任何 Domain Model 都會隨時間的推進與需求的改變而演化,在 `Ch.13 Evolving a Design and Keep it Clean` 將會深入討論。 ### Creating Context Maps 當設計出 Bounded Context 後,我們還必須設計出各 Bounded Context 溝通方式,此時並不需要對實作部分太講究,只需有整體概念即可,在 DDD 稱為 **Context Map**。   如同旅行時使用的 route map 一樣,它並沒有顯示各城市細節,也沒有顯示以何種交通工具來往,它只顯示出下一站的目的地,讓你可以根據它設計出旅行計劃。  如同 route map 一樣,Context Map 主要在顯示各 Bounded Context 間的關係而非實作細節,可以大致看成 `Order-taking` context 是 upstream,而 `Shipping` context 是 downstream。 很明顯的當兩個 Bounded Context 要溝通時,必須同時遵守一定的通訊協定,通常 upstream 要較高的影響力決定,但有時候因為 downstream 是 legacy system 無法輕易改變,有可能 upstream 必須妥協,或種中間有 adapter 進行轉換,詳細會在 ` Ch.03 A Functional Architecture` 中討論。 雖然目前的 `訂單系統` 只需一個 Context Map 就能搞定,但實務上會有很多 Context Map 用來表示各自 Subsystem。 ### Focusing on the Most Important Bounded Context 當分割出眾多 Bounded Context 之後,要從哪個 Bounded Context 開始開發呢 ? * **Core Domain**:具有企業核心優勢,能使企業賺錢的 Domain * **Supportive Domain**:必須但非核心的 Domain * **Generic Domain**:對企業並非必須的 Domain 以本例而言,`Order-taking` 與 `Shipping` 可視為 Core Domain,而 `billing` 則為 Supportive Domain。 ## Creating a Ubiquitous Language 根據 DDD 第四條 guideline: > 為 Developer Team 與 Domain Expert 間建立 Ubiquitous Language 因為 DDD 強調 Code 與 Domain Expert 必須使用相同的 Mental Model,如 Domain Expert 使用 `Order` ,在 Domain Model 裡也必須稱為 `Order`,Developer Team 不能根據自己的喜好使用其他字彙。 除此之外,Domain Model 也不該出現 Domain Expert 的 Mental Model 中所沒有字彙,如 `OrderFactory`、`OrderManager`、`OrderHelper` ... 等,當然這些字彙在 codebase 中可能會出現,但不應該在 Domain Model 出現,因為 Domain Expert 並不了解。 由於要讓所有人都能了解 Domain Model,因此必須統一大家的觀念與字彙,在 DDD 稱為 **Ubiquitous Language**,白話就是 everywhere language,也就是 Developer Team 與 Domain Expert 在 Domain Model 所使用的共同字彙,除此之外,既然是 everywhere,在 codebase 也該使用相同字彙。 不要以為 Ubiquitous Language 只是 Domain Expert 所定義而已,這是整個 team 共同制定的,且 Ubiquitous Language 也不是一成不變,而是會隨著 project 的進行而加入新的觀念或字彙,在本書後續章節也會看到。 雖然有了共識的 Ubiqutious Language,但有時同一個字彙在不同 Bounded Context 可能有不同定義,如同 `方言` 一般。 如 OOP 的 `class` 與 CSS 的 `class` 就是完全不同意義。 如 `order` 在不同組織中的意義也可能有些許差異,在 `物流部` 關心的是 `產品數量`,而 `帳務部` 則關心 `產品單價` 與 `總金額`,所以儘管有了 Ubiquitous Language 後,仍必須注意其 Bounded Context,否則可能造成誤解。 ## Summarizing the Concepts of Domain-Driven Design * **Domain**:我們要解決的問題中所使用的專業知識領域,會有 Domain Expert 存在 * **Domain Model**:在 Solution Space 內經過簡化的模型 * **Ubiquitous Language**:Domain 中所有 team member 都使用的共同字彙 * **Bounded Context**:在 Solution Space 中有明確 boundary 的 subsystem,且存在 Ubiquitous Language 的 `方言` * **Context Map**:顯示 Bounded Context 之間關係 * **Domain Event**:系統中會發生的 Event,會以 `過去式` 表示,且會觸發其他 Command * **Command**:執行 Process 或觸發 Event,可由其他 Event 觸發 ## Wrapping Up 一開始我們強調 Shared Mental Model 的重要性,接著提出 DDD 四個 guideline: * `將焦點放在 Business Event 與 Workflow 而非 Data Structure` * `將 Problem Domain 切割成更小的 Subdomain` * `根據 Subdomain 建立 Domain Model` * `為 Developer Team 與 Domain Expert 間建立 Ubiquitous Language` ### Event and Process  Event Storm 中找出了 Domain 中重要的 Event,如: * `Order form received` event 觸發 `Place order` process * `Place order` process 執行完觸發 `Order Placed` event * `Order Placed` event 觸發 `Ship Order` process 與 `billing` process ### Subdomain and Bounded Context  You: > Ollie (訂單部),你知道 `billing` process 如何運作嗎 ? Ollie (訂單部): > 一點點,但詳細內容要問 `物流部` 的 Sam 由此可知 Billing 為另外一個 Domain,也就是所謂的: > A Domain is what a Domain Expert is expert in  接著我們可將 Problem Space 的 3 個 Subdomain 以 Solution Space 的 3 個 Bounded Context 對應。  以 Context Map 顯示 Bounded Context 之間的關係。 至於何者為 Core Domain,這應該由總經理 Max 決定,我們假設 `Order-taking` Context 是 Core Domain,將由此 Bounded Context 開始開發。 ### The Ubiquitous Language 目前我們已經有如 `order form`、`quote`、`order` 等專有名詞,且隨著專案的進行,還會出現更多專有名詞,為了讓團隊有共識,且讓新進成員快速上手,可建立 live document 或 Wiki 之類加以記錄這些專有名詞及其定義。 ### What's Next ? 雖然目前對於 `訂單系統` 已經有不少了解,但仍缺乏一些東西: * `Order-processing` workflow 詳細的 input 與 output 為何 ? * `Order-processing` workflow 與哪些 Bounded Context 相關呢 ? * `物流部` 的 `order` 與 `帳務部` 的 `order` 認知詳細差異在哪 ? 在 `Ch.02 Understanding the Domain` 將試圖討論以上問題。 ## Conclusion * 對於 DDD 完全沒概念的初學者,可藉由本章對於 DDD 手法有初步認識 * 對於 DDD 最經典專有名詞如 Event Storm、Bounded Context、Abiquitous Language ... 也能有初步認識 ## Reference [Scott Wlaschin](https://twitter.com/ScottWlaschin?ref_src=twsrc%5Egoogle%7Ctwcamp%5Eserp%7Ctwgr%5Eauthor), [Domain Modeling Made Function Preface](https://pragprog.com/book/swdddf/domain-modeling-made-functional)
×
Sign in
Email
Password
Forgot password
or
By clicking below, you agree to our
terms of service
.
Sign in via Facebook
Sign in via Twitter
Sign in via GitHub
Sign in via Dropbox
Sign in with Wallet
Wallet (
)
Connect another wallet
New to HackMD?
Sign up