###### tags: `領域驅動開發` `DDD` `Domain Driven Design` # DDD實戰-Module ### 一、關於Module 在闡述Module前,讓我們先來比對非物件化設計與物件化設計的差異性。看到下圖左,在非物件化的傳統設計,資料與方法操作上是整個拆開設計,在資料流複雜外我們可以看相依性高,偶合的狀況也較高。 接著討論物件化設計部分,先不論封裝等物件操作技巧,單純根據資料與方法關係設計出物件(物件特性:資料(Data) 物件行為:方法(Function)),我們可以看到原本互相依賴的Fun關係可各自獨立使用,在資料流上也較為單純。 ![](https://i.imgur.com/eCnw2lV.png) 物件化後,雖然在資料操作面上簡化許多,但隨著專案功能性逐漸增加,物件與功能處理流程上會趨近越複雜,如下圖為一個簡易的系統,此系統根據外部Sensor系統獲取、過濾與計算資料,在沒模組化的設計前提,可以看到Control與Data Flow呈現一個較零散的關係狀態 ![](https://i.imgur.com/tYs6Ybt.png) 此時我們根據Sensor功能額外設計一個Sensor模組如下,此時我們就可以針對Sensor相對應的資料模組將之封裝在模組內,此模組對外則就單純開放運算(calculation)與過濾(filtering)的功能。 模組化後可以明顯看出控制與資料流的彼此相依性值,因此降低了修改與擴充狀況的互相影響性值。 ![](https://i.imgur.com/bQWCuAH.png) 模組化指是將**系統功能程序作分離獨立(特定功能class的容器)**,除了功能獨立外,也強調設計上可以根據功能隨意抽換模組。達到高內聚、低耦合的目的,進而提高開發者的生產力(將複雜的功能拆分管理)。並讓程式碼能夠透過引用的方式來重複使用,提升重用性(Reusable) > Modular design allows code to remain agile in the face of ever-changing requirements. --- ### 二、構成Module考量五要素 - Propose:Module功能目標單一職責,盡量不要與其他模組設計有太多相依關係。 - Interface:模組功能API所提供使用方式要簡潔易懂,通常User不需要去了解實際內部的實作方式,只需專注在確定輸入什麼,會輸出什麼可達到什麼功能。 - Encapsulation:封裝模組,除了讓不暴露資料結構讓使用者亂使用外,對於在修改細節上也能較不容易直接影響使用端。另外再使用抽象實作上,物件抽象化多少還是難以避免Leaky Abstractions的問題。 - Implementation:實作上除了考量功能正確性外還需考慮效能、測試與功能架構程式碼最小化。 - Connection(關聯性):呼應Propose功能單一職責,將與其他模組相依性最小化。 --- ### 三、Package Vs Module 在了解Module構成要素後~隨著專案模組(Module)的增加,將難以管理及問題的追蹤,這時候就能將模組(Module)打包成套件(Package),利用其階層式的結構來彈性規劃模組(Module)。 - Module:單一功能模組 - Package:多Module組成 ### 四、一般Module設計方法 在非以領域事件為出發點的設計上,大部分狀況會根據功能設計成物件與介面使用。下圖為Zebra SDK Package的Module列項,我們可以大致分得出,他的Module設計就是根據功能性值去區分(graphics, certificate...) ![](https://i.imgur.com/9r6cSWk.png) 接著我們點近discovery看提供什麼API功能,可以看到可以使用的介面功能以及相關功能可使用的功能物件。 ![](https://i.imgur.com/qEtuOCR.png) 在點進個別更詳細功能介紹,我們就會看到這物件功能具有什麼資料特質、物件實體化須提供什麼參數以及此物件可使用哪些方法。 - Field: address,IP or Mac Adress - Constructor:物件實體化須提供印表機的IP or Mac Adress - Method:可使用的物件功能,在此例看起來需實作getConnection功能 ![](https://i.imgur.com/BU8pxp3.png) 上述為印表機找到印表機裝置功能的模組化介紹 --- ### 五、未使用DDD的一般Service服務設計 上述一般Module設計概念聊完,在聊DDD Module設計之前先來聊一下在一般未使用DDD領域設計的服務系統會如何設計。 在對大部分的開發者,一開始習慣設計都以數據為考量的集中式設計架構。設計架構上會出現比較常見到的分層式設計,大致分成Controller, Service, Repositories, Models - xxx/Model - 數據庫Model、Request Model, Response Model - xxx/Controller - 給Client端的API第一時間接口,提供Get,Post,Delete,Update API。除了此在此層一般都會安插屬性驗證Client第一時間傳過來的資料是否正確。並作實際的DTO(Object 與DB Model Mapping)轉換。 - xxx/Service - 這層基本上就是作商業邏輯的處哩,進到此層的資料基本上都是做完DTO轉換,在此層通常會作實際的資料邏輯處理處理完後再往DB方向送。 - xxx/Repositorie - DataSource(DBContext)上一層,一般會除了實際面DataSource讀寫變更操作外,Source 資料Join處理也會在此層處理。一般多這一層都是為了隔開DataSource的來源切換,不管置換不同的DB系統,或是Source改成Shared Prefs,都可快速置換資料來源。 ![](https://i.imgur.com/jh6V5V1.png) --- ![](https://i.imgur.com/E0aH4K0.png) --- ### 六、在DDD世界中的Module設計 上述稍微帶過Module的設計概念後,接著探討在DDD世界裡,Module的設計概念如何~大致分成幾個探討議題 #### 1.DDD設計步驟流程 ![](https://i.imgur.com/UxkUrru.png) - 在探討需求架構DDD設計的第一步,就是根據需求情境列出事件風暴(Event storming),並在事件風暴中的用戶操作、事件、以及依賴關係根據這些要素設計歸納出領域與實體。 - 接著第二步在領域實體之間找尋彼此務的關聯性,將具有相關的實體組合成聚合(Aggregate),同時確定聚合根(Aggregate Root)。在聚合根行程時,基本上第一層邊界(邏輯邊界-虛線)也會跟著產生,他們會在同一個服務器中運行。 - 當聚合規劃好後~會根據業務及語意邊界等因素,將一個或多個聚合規劃訂製在一個限界上下文內(服務邊界),形成領域模型。 ![](https://i.imgur.com/m9zqq3B.jpg) #### 2.程式碼一級目錄架構 ![](https://i.imgur.com/Lo710em.png) - Interface(API Interface) - 給使用者API介面,使用者透過Restful請求,將資料傳到此層,解析用戶傳送的請求資訊,資料的組裝、資料傳輸格式以及 Facade 介面等代碼都會放在這一層目錄裡。 - Application - 他有點像是原先集中式設計Service的功能,實作所有相依於指定前端之使用案例的地方。 例如,與 Web API 服務相關的實作。若使用的是 CQRS 方法,它便會包含查詢、微服務接受的命令,甚至是微服務之間的事件驅動通訊 (整合事件)。 - Domain - 它主要存放領域層核心業務邏輯相關的代碼。領域層可以包含多個聚合代碼包,它們共同實現領域模型的核心業務邏輯。聚合以及聚合內的實體、方法、領域服務和事件等代碼會放在這一層目錄裡。 - Infrastructure - 它主要存放基礎資源服務相關的代碼,為其它各層提供的通用技術能力、三方套裝軟體、資料庫服務、配置和基礎資源服務的代碼都會放在這一層目錄裡。 #### 3.在DDD Module準則 例子:如何對電商平台上的顧客進行模塊設計 對於顧客來說,一般須要維護顧客的 - 個人訊息 - 收穫地址 - 付款方法 這三個之間的關係是緊密相關,不可獨立存在,我們根據這三點抽象出三個Aggregate - Customer 個人訊息 - AddressBook 收穫地址 - Wallet 付款方法 那該如何去放置這些Aggregate,是針對每一個Aggregate作資料夾分類還是這三個Aggregate放同一格資料夾?基本上這三個Aggregate就是一個Custer Module,所以都會放到Custer Module資料夾內。 ![](https://i.imgur.com/fkBiQjS.png) 當整理出Aggregate與Module後,接著會開始根據各Module去實作事件應用處理 基本上我們在DDD模塊的設計上有幾個注意要點 - Module應該要和Domain概念一致:一般一組聚合集成(領域),我們會相對應建立一個Module。 - 根據通用語言來命名:模組命名要一眼就看出這是在做什麼的。 - 模組設計盡量鬆偶合:盡量與其他模組不要有太多的偶合,若有也許在領域設計上還沒切得很乾淨。 - 如果有PeerModule或父子Module出現,盡量避免循環相依。 #### 4.關於Module命名 ![](https://i.imgur.com/jLYf3Ot.png) #### 5.Module界線與限界上下文不同 為了對領域模型中進行準確建模,需要將領域模型劃分成多個子域,每個子域對應一個或多個限界區域。 模塊。所以,從子域到限界某些再到模塊,應該是依次包含關係。 ![](https://i.imgur.com/dRmq7U3.png) ### 補充 #### 補充一 - Abstracion: - 將真實世界物體與事件的大量資訊縮減一個概念或是一個現象的資訊含量來將其廣義化,保存和一特定目的有關的資訊。例如,將一個皮製的足球抽象化成一個球,只保留一般球的屬性(形狀)和行為(滾)等資訊。 - Leaky Abstractions - 所有非不證自明的抽象概念,都有某種程度的疏漏。例如TCP雖簡化(抽象化)網路行為,設計上也保證網路傳送過程中不遺漏資訊,但不保證就真的能完整傳到資訊,例如我們無法避開海底電纜被魚咬斷因此斷訊的狀況。 ### 參考 [範例](https://github.com/kgrzybek/modular-monolith-with-ddd#32-module-level-view) [The 5 Essential Elements of Modular Software Design](https://shanebdavis.medium.com/the-5-essential-elements-of-modular-software-design-6b333918e543) [The Law of Leaky Abstractions](https://www.joelonsoftware.com/2002/11/11/the-law-of-leaky-abstractions/) [The Three Principles of Excellent API Design](https://nordicapis.com/the-three-principles-of-excellent-api-design/) [解析Python模組(Module)和套件(Package)的概念](https://www.learncodewithmike.com/2020/01/python-module-and-package.html) [Module Design](http://www.the-software-experts.com/e_dta-sw-design-module.php) [Domain Events vs. Integration Events in Domain-Driven Design and microservices architectures](https://devblogs.microsoft.com/cesardelatorre/domain-events-vs-integration-events-in-domain-driven-design-and-microservices-architectures/) [DDD理论学习系列(13)-- 模块](https://www.cnblogs.com/sheng-jie/p/7266557.html) [DDD理论学习系列——案例及目录](https://www.cnblogs.com/sheng-jie/p/6931646.html)