看到網上討論 DDD 的文章越來越多,咱也不能甘于人后啊,以下是我對 DDD 的個人理解,短小精悍,不喜忽噴,
解決什么問題
傳統模式,產品評審結束,開發人員就憑經驗拆分模塊,設計資料結構,然后寫業務邏輯實作功能,問題在于,不同人的經驗、理念不一樣,同樣的產品需求,最終的技術實作也會不一樣;就算是同一個人,可能不同時候接手同樣的需求,也會出來不同的設計,究其原因,很多細節之處都是拍腦袋或按個人喜好,或以無所謂的心態處理了,得出的自然是各式各樣的結果,往往這些結果是無法令人滿意的,這又觸發了重構的沖動,然而由于沒有一套標準的原則和方法論,所謂的重構只不過是周而復始,盲人探路,
DDD(領域驅動設計)的出現,猶如黑暗中的燈塔,點燃了希望,指引了方向!
其實在傳統模式中,我們已經有領域概念了,因為領域概念是天然的,自然地充斥在需求的各個角落,在設計資料庫的時候,開發人員肯定是以自己經驗,按領域模型構建表結構的,比如用戶表、訂單表、訂單子表等等,這里的用戶、訂單、訂單子項就是領域模型,但領域驅動到此為止,一旦資料庫設計完畢,以資料為驅動的開發模式就粉墨登場并貫穿整個專案周期,所有操作都開始圍繞資料庫作增刪改查,到后期資料量大起來,業務外拓,怎么迭代、怎么重構又是公說公有理婆說婆有理,一團漿糊,可能每個人說的都有道理,各自的方案都是可行的,但問題就出在都有道理上,誰也說服不了誰,于是我們迫切地需要一套方法,統一思路,統一方向,不同的人借助它都能設計出較為合理的架構,且相互之間可以認同,就算有調整也是細節而非大的層面,
DDD 就是這樣一套方法,如果高內聚、低耦合是理想的架構,那么 DDD 就是為了實作它形成的一套方法論,它作用于需求分析、產品分解、架構設計、業務撰寫等專案環節,開發人員至少從產品分解環節就要介入,借助 DDD,你會發現混沌迷蒙的代碼世界從來沒有如此清晰,一條康莊大道在你眼前鋪開,一路延伸到那看不見的遠方,
概念
首先來看下領域模型的四個概念:
值物件:不可變,意即改變其狀態等于是得到了一個新的值物件,外部不是以參考去參考值物件(好怪),而是直接使用它本身,(題外話:C# 9 引入的record有一點值物件的意思)
物體:有狀態(即有生命周期),有識別符號(比如 id),
聚合:聚合是業務和邏輯緊密關聯的物體和值物件組合而成,聚合是資料修改和持久化的基本單元,
聚合根:也是物體,同時是聚合的管理者,在聚合內部,負責協調物體和值物件按照固定的業務規則協同完成共同的業務邏輯;在聚合之間,它是聚合對外的介面人,以聚合根id的方式接受外部請求和任務,實作背景關系中的聚合之間的業務協同,
DDD 中的領域模型是充血模型,
舉例
訂單有待付款、已付款、已完成等狀態,是物體;
訂單還有訂單子項集合(每一項對應一個商品),每個訂單子項可以增減數量(也即改了屬性,但還是那個訂單子項),所以也是物體;
訂單有識訓地址,識訓地址由省、市、區、具體住址組成,各部分可獨立設定,當修改了其中一項,自然就是一個新地址了,所以識訓地址是值物件;
訂單子項和識訓地址依賴于訂單,不能獨立存在,外部自然也不能繞過訂單直接操作到它們,因此訂單、訂單子項、識訓地址可作為一個聚合,訂單是聚合根,
有同學會說,不對啊,識訓地址在我的模塊里是可以修改維護的呀,修改之后記錄還是原來的記錄(同個物件),只是內容不一樣呀(狀態變化),按你的說法,識訓地址應該是物體才對,——這就是不同的界限背景關系導致同樣的業務概念表現為不同的領域模型,甚至在不同的聚合中也可以不同——假設訂單參考了識訓地址(用戶選擇了識訓地址串列中的第一條北京王府井),如果此時有個操作更改了該識訓地址(用戶修改了他的第一條識訓地址,從北京王府井改為杭州延安路),那么原來已確定的訂單地址會跟著變嗎?顯然不會,這就是值物件的概念,其實值物件和物體同我們熟悉的值型別、參考型別表現行為是差不多的,
對于值物件以及其它所有概念,我們要理解,而不是刻意套用,
我們再來想,購物車是否可以劃入訂單聚合里,畢竟感覺上購物車就是為訂單服務的,這里有個簡單的判斷準則:領域模型是否可以獨立訪問,是就是聚合,如果沒有訂單,購物車是可以存在的;反之,用戶可以直接下單,而不需要先把商品加入到購物車;所以購物車和訂單分屬兩個聚合,
DDD 是以高內聚、低耦合為指向的,可以說,這兩者是絕大多數架構水平的評判標準,自然也是 DDD 的理論基礎,
低耦合:模塊之間不依賴對方的具體實作,我們熟悉的面向介面編程、IOC等機制就是為了貫徹它而來的,曾經流行一時的幾十種面向物件設計模式大多也是為了達到低耦合的目的,
高內聚:模塊只負責自己應該負責的職責,高內聚與低耦合關注點不同,它是劃分模塊職責的原則,
一般來講,高內聚、低耦合是相輔相成的,高內聚決定了必須低耦合(A不能直接呼叫B,否則A可以行使B的職責),低耦合要求高內聚(既然A、B互不關聯,那必然職責得是分離的),
許多人對它們區分不清,特別是高內聚(畢竟低耦合一直被強調),這里簡單說明:比如 A、B 能否直接相互依賴,這是低耦合的考量;A、B 是否能整合成C,或者A是否需要拆分為 C、D,這是高內聚的考量,領域分析時,高內聚會考慮多一點,構建代碼時,就要考慮這些職責不同的模型如何協同作業,也即如何耦合到一起,
DDD 提出了兩種模式:
領域服務:當領域中的某個操作程序或轉換程序不是物體或值物件的職責時,此時我們便成該將該操作放在一個單獨的介面中,即領域服務, 領域服務是無狀態的,比如報表資料的查詢,很難說誰誰的職責;一般我們可以將大部分的查詢操作放到領域服務中,
領域事件:一般指子域內部事件,當A模塊執行完自己的操作后,觸發事件,任何監聽該事件的模塊開始執行自己的操作,
這兩種模式又引出了CQRS的概念,如此又可以自然地去考量基礎層資料源的劃分和同步方案……
上文提到的界限背景關系(BC)/子域:系統內部按照不同業務目的進行劃分的模塊,一般來說,一個 BC 對應一個子域,對應一個微服務,子域又分核心域、支撐域、通用域,這又是 DDD 創造的一些概念,目的仍是為了按一定特征劃分業務關系,千萬不要覺得這是什么高深術語,
另外提一嘴,微服務之間如果通過 SDK/API 方式互相呼叫的話,首先要判斷是否子域劃分得有問題,一個業務不應該強關聯多個微服務;對于必須跨服務執行的情況,除直接呼叫外,也可以考慮事件總線的方案,但是否值得這么做需要考量,一般事件總線用于弱關聯服務之間,比如訂單服務、訊息推送服務,一旦有顧客下單,馬上給商家推送訊息,它們之間雖然有順序關系,但上游服務并不關心下游服務是否執行到位;對于強關聯服務,要考慮事務的最終一致性,
相關資料
如何運用領域驅動設計 - 值物件 (文中說的盡量避免使用基元型別不敢茍同,屬性若是基元型別就能表示清楚且滿足功能需求的沒必要非得封裝為值型別,而且最后一層的屬性肯定只能是基元型別)
DDD之4聚合和聚合根
MASA Framework - DDD設計(1)
eShopOnContainers 知多少8:Ordering microservice
轉載請註明出處,本文鏈接:https://www.uj5u.com/ruanti/430230.html
標籤:領域驅動設計
上一篇:基于用戶權限的動態選單生成
下一篇:設計模式-代理模式
