作者 | 林寧
?領域到底是什么?
對領域這個詞的理解就是 DDD 入門的第一個難關。我們有時會被客戶問到,領域到底是什么?首先要清晰地知道領域是什么,才能劃分核心域、支撐域和通用域。換句話說,構成領域的要素是什么呢?
領域是一個非常抽象的詞匯,我們需要先對其具象化。在英語的語境中,“Domain” 其實就是業務,指的是現實生活中的各種事務。處理稅務、記賬、售貨記錄等,這些都是領域。
于是,我們給領域下了一個定義:
領域(Domain)是業務相關知識的集合。
通俗來說,領域就是業務知識。業務有一些內在規則,存在專業性,比如財務、CRM、OA、電商等不同領域的業務規則不同。計算機只是業務規則的自動化。更加具體來說,構成領域的要素就是特定的業務場景。
通過對業務的場景劃分,再對其分類,就是我們的子域。
- 核心域:那些對業務極其重要的場景,內容社區應用,就是提問、看帖、回復。
- 支撐域:那些對重要業務支持的場景,比如登錄、找回密碼等場景。
- 通用域:那些已經成為相對獨立的支撐業務場景,比如實名驗證、人機校驗(以前是支撐,現在可以是通用)。
領域和上下文是什么關系?
如果領域的構成要件是場景,上下文的構成要件是模型,那么領域和上下文之間就沒有包含和被包含關系。
也不存在一個領域是否對應多個上下文的關系。
他們構成:上下文支撐領域的關系,領域導出上下文的關系。
DDD 軟件建模就是業務問題和解決方案之間的橋梁。領域是問題,設計出來的模型是解的一部分。因此,問題和解形如 x 和 f(x) 的關系,f = 軟件建模過程。
舉個例子來說,某個電商網站有多個渠道,零售、批發、企業采購等多個場景的業務,這是他們的領域。對于研發工程師來說,他們會最終設計出訂單、商品等模型上下文,來支持這些領域。
聚合如何持久化?
聚合被賦予了兩個責任:
- 負責業務的一致性。?
- 負責數據的整體存儲。?
而其持久化是一個老大難問題。
關于業務的一致性,Eric DDD 給我們描述了一種理想情景。只要把業務一致的一組模型從數據庫中統一獲取到,對其做業務修改,然后再持久化回去,就可以避免業務的一致性被破壞。
業務的一致性可以這樣理解。我們有訂單和訂單項,訂單的總價由訂單項計算得來。如果不長眼的程序員把訂單項直接修改了,而不更新訂單,就會帶來 bug。
但是,遺憾的是我們的內存不是無限大,而且數據會在斷電后丟失。我們必須把數據從磁盤中讀取出來,而磁盤的訪問速度很慢。數據在磁盤中的組織形式使用了集合+關聯的方式存放,這是由于我們為了降低數據冗余和方便查詢而不得已為之。這就是關系模型和對象模型的差異,而不得不采用一些技術方法轉換(ORM)。
而數據的整體存儲,讓聚合的持久化變得困難和性能低下。
一個簡單的道理是,我們只需要一個橘子,卻總想把橘子樹搬來搬去,雖然摘橘子需要通過橘子樹。
充血模型為什么不符合編程習慣?
充血模型已經是很多 DDD 實踐者的潛在認知,簡單來說就是把業務行為放到模型中。
這種做法看似滿足了面向對象的實踐,但是在實際工作中,它并不方便,甚至有些別扭。在培訓中,有學員找我們說,學了 DDD 之后不會寫代碼了,甚至忘記之前的代碼該如何編寫。
極端一點的例子,還會有人在聚合根中調用倉儲來實現聚合的存儲。這時,他們發現矛盾在于 JPA 的存儲需要使用實體的類型信息,這時候便束手無策了。
在辯證唯物主義認識論中,一個行為構成的要件是:主體 + 動詞 + 客體。
在英語學習中,主謂賓結構的主體是主語,客體就是賓語。甚至,主系表結構也滿足這個道理。主語是主體,表語是主體的屬性,也是客體。
“太陽是圓的”。指的是,太陽的形狀是圓的。太陽是主體,“是” 作為邏輯謂詞可以認為是動詞,“圓的”是太陽的外觀屬性。
合適的充血模型是給 “主體”充血,給客體貧血。特殊的情況是,當一個模型操作它的屬性的時候,它也可以是主體。因此,給領域模型的操作能力,應該僅限于操作自己的屬性。而領域模型的構建、業務處理、持久化應該交給主體來做。
一個有意思的悖論就是,不合適的充血模型就像讓一張桌子讓它自己把自己搬到樓上去,我們難描述這種行為。更好的做法不是去找一個搬運工去搬這個桌子么,這次行為的主體就是搬運工,客體就是這個桌子。
如何清晰的分層?
分層有兩個原則:
- 分層是有明確目的,沒有目的的分層會帶來額外的問題。?
- 分層需要考慮框架、庫的實現,否則容易帶了 “千層餅架構”。?
分層的目的為了隔離差異,沒有差異而進行的分層就是浪費。由于差異的出現,每層所對應的客體就發生了變化。
- 接入層:處理接入協議,這個時候還不知道領域信息,客體就是數據包的不同形式。?
- 應用層:處理業務場景,比如用戶注冊、添加用戶、導入用戶等,客體就是一些用例對象。?
- 領域層:處理通用領域能力,比如創建用戶,客體主要就是領域模型。?
- 技術設施層:為上層提供技術實現,并不知道領域層的信息。比如 JPA 是一種持久化實現,需要從領域層輸入對象的類型信息和數據信息,客體就是泛型對象。?
多對多關系一般怎么處理?
多對多就是客體的含混不清,迷失了中間模型。
一個訂單可以有多個商品,實際上是一個訂單有多個訂單項。
- 一個用戶可以加入多個文檔協作編輯,實際上是一個用戶可以成為多個文檔的參與人。?
- 一個用戶可以加入多個用戶組,實際上是一個用戶可以在多個用戶組成為成員。?
辨明客體,可以讓代碼變得清晰、簡單、解耦。在現實中,一個老板可以有多個公司,一個公司也可以由多個老板投資。他們之間的多對多關系是通過 “股東” 這個客體來承載的。
在有限責任的公司中,股東身份和老板的個人身份(自然人)相互獨立,并得到司法支持。?