領域驅動設計 (DDD) 的思考

寫在前面

打開 DDD 相關的書籍,你會被一系列生硬、高深的概念充斥,拜讀完畢,滿頭霧水。這不是你的問題,而是 DDD 自己的問題,表現形式太概念化。學習它的內核,就不要被它給出的概念所迷惑,而要去思索這些概念背後所蘊含的設計原則,多問一些爲何,本質無外乎是 SOLID。最重要的,要學會運用設計原則去解決問題,而非所謂的 「設計規範」。web

本文將會以系列解答的方式展開,由淺入深,篇幅不長,無妨一看。緩存

啥是 DDD?

本質上是一種方法論,提供了一套系統開發的設計方法。面對須要解決的問題,從複雜的現實中抽象出業務模型的思惟方式與實踐技巧。初衷是清晰設計思路,規範設計過程。架構

啥是驅動?

DDD 強調是說得先把 「領域」 中涉及到的數據、流程、規則等都弄明白了,而後以面向對象的觀點爲其創建一個模型(即領域模型),而這個模型,決定了你將用什麼技術、什麼架構、什麼平臺來實現這個系統。因此技術在這個過程當中是 「被動的」,是被 「選來」 實現 「領域模型」 的。對於項目的成敗,技術不是決定性因素,領域模型是否符合事物的本質纔是關鍵。app

能夠看出,領域驅動設計的出發點是業務導向,技術服務於業務。運維

我有誤解?

學習 DDD 有一些常見的誤區。第一個要避免的就是,你必需要清楚,DDD 對於工程領域沒有提出多麼創新的技法,它更可能是把前人在生產系統中用慣的技法概括,總結,包裝了一下 ——dom

  1. DDD 拋開它晦澀的表達和術語,內核無外乎是 SOLID。書中的技法不是做者的發明創造或獨門祕籍,而是當時業已存在並已在使用的作法,只不過沒有被系統地加以記錄 —— 換而言之,只要遵循 SOLID 的原則,這些所謂的概念技法徹底可能在無心識的狀態下自發出如今生產代碼中。
  2. 這些技法在各類大型系統被屢次使用。換而言之,只要你接觸足夠多的代碼、足夠多的項目,必然會接觸到這些 DDD 的實際應用。只不過在看過 DDD 一書以前,你可能意識不到這是一個成體系的設計手段。—— 若是你沒法理解這樣一個設計理論,看實際生產代碼的應用場景是惟一真正有效的方法,畢竟設計理論自己就是從具體代碼中總結出來的。即便你以爲本身懂了,抽象思惟和開發經驗也可能還未達到正確使用它的水平。
  3. DDD 最大的價值在於對設計手段進行了有效的整理,造成了一個完整的系統設計規範,但過於晦澀和概念化的表述,也幾乎消解了它對業界的貢獻。

啥時候用?

你可能認爲 DDD 是一把 「瑞士軍刀」,可以解決全部的設計問題,而實際上 「DDD 只是一把錘子」,有個諺語叫作 「若是你手裏有一把錘子,那麼全部的問題都變成了釘子」,若是你拿着 DDD 這把錘子處處去敲,要麼東西被敲壞,要麼就不起做用。curl

爲何說 DDD 只是一把錘子呢?做者明確指出,DDD 只適合業務複雜度很大的場景,不適用於技術複雜性很大但業務領域複雜性很低的場景。能夠看出,DDD 只專一一個領域,高複雜業務 —— 經過它能夠爲你的系統創建一個核心、穩定的領域模型,靈活、可擴展的架構。分佈式

DDD 是擁抱複雜的,擁抱變化的,但自己也是有成本有前提的;簡單系統,不必搞這麼複雜,強上 DDD 就是一種反模式了。因此,當你遇到一個問題就想到 DDD 的時候,必定要注意 「DDD 只是一把錘子」,不要拿着這把錘子處處去敲!工具

啥是複雜?

如何判斷業務是否複雜,判斷依據不勝繁數。在我看來,沒那麼複雜,就兩個:單元測試

  • 寬度:鏈路廣度大,關注多個緯度的消息來源,覆蓋了較多的業務場景
  • 深度:鏈路深度深,關注對象整個的生命週期,從數據建立、到變動、再到後期運維,流程運轉長

只要知足其中一個,我認爲就是複雜的。

具體解決啥?

DDD 並不是 「銀彈」,天然也不是解決全部疑難雜症的 「靈丹妙藥」。在我看來,它只解決一個問題:過分耦合。

爲啥會耦合?

業務初期,系統功能大都很是簡單,CRUD 就能知足,此時的系統是清晰的。隨着業務的不斷演化,系統的頻繁迭代,代碼邏輯變得愈來愈複雜,系統也愈來愈冗餘。模塊彼此關聯,誰都很難說清模塊的具體功能意圖是啥;修改一個功能,每每光回溯該功能須要的修改點就須要很長時間,更別提修改帶來的不可預知的影響面。

歸根到底在於系統架構不清晰,劃分出來的模塊內聚度低、高耦合,致使代碼很差複用、很差擴展、很差運維。

咋解決耦合?

第一種解決方案:按照演進式設計的理論,讓系統的設計隨着業務的演進而增加。這固然是可行的,工程實踐中的重構、持續集成就能夠對付各類混亂問題。問題在於,只是沒有章法的、小範圍的代碼重構,很難具有通用型,容易變成了重構者的自娛自樂,代碼繼續腐敗,從新重構…… 無休止的循環

第二種解決方案:從新抽象,如何抽象,DDD 建議咱們左右開弓:一、分治(實體對象、值對象、聚合根);二、分層(展現、應用、領域、通用)

咋作分治

核心概念三句話:

  • 聚合根:一組相關對象的集合,做爲一個總體被外界訪問。聚合根的 ID 全局惟一
  • 實體:有生命週期,有狀態,經過 ID 進行惟一標識
  • 值對象:屬性,配合描述實體的狀態

核心關係一句話:

  • 經過聚合根來引用實體,掛載值對象,對外屏蔽內部的實體邏輯

talk is cheap,show me the code

//聚合根
 class Order {
     public String id;//訂單ID,全局惟一
     public Address customerAddress;//配送地址
     public List<Item> items;//商品信息
     public Pay pay;//支付信息
     public LogisticsDetail logisticsDetail;//物流信息
     public Pingjia pingjia;//評價信息
 }
 
 //實體
 class Item {
     public Long id; //商品ID,實體主鍵,Order內惟一
     public String name;//商品名
     public float price;//價格
     public int count;//數量
 }
 
   //實體
 class Pay {
     public Long id; //支付ID,實體主鍵,Pay內惟一
     public String source;//支付方式
     public int currency;//幣種
     public float total;//價格
 }
 
  //實體
 class LogisticsDetail {
     public Long id; //物流ID,實體主鍵,LogisticsDetail內惟一
     public int cpCode;//物流公司
     public String mailNo;//物流單
     public float status;//當前狀態
 }
 
 //實體
 class Pingjia {
     public Long id; //評價ID,實體主鍵,Pingjia內惟一
     public String desc;//描述
     public byte[] image;//圖片
 }
 
 //值對象
 class Address{
     public String province;//省
     public String city;//市
     public String county;//區
 }

能夠看到,經過業務限界,DDD 將大型複雜的 DO 分解爲若干簡單的 DO,從而保證 DO 的擴展性、靈活性。具體到 DB 層面,須要五張表:

圖片描述

但若是業務沒有這麼複雜,我依然推薦 DO 的 4 要素設計:基礎要素、核心要素、擴展要素以及冗餘要素。就拿 open 店面綁定的 *App 設計舉例:

  • 基礎要素:createdTime、modifyTime
  • 核心要素:developerId、appId、businessId、ePoiId、poiId
  • 擴展要素:開發者的聯繫電話、店面的聯繫電話
  • 冗餘要素:feature

於此,咱們能夠概括出領域模型設計的通常步驟:

  • 根據需求劃分出初步的業務域
  • 識別出哪些領域是實體,哪些是值對象
  • 對實體、值對象進行聚合,劃分出聚合根
  • 工程實踐中檢驗模型的合理性,倒推模型中不足的地方並從新概括

這裏多說一句,若是你讀過金字塔原理的話,會發現思惟方式分爲兩種:概括性思惟和演繹性思惟。人的原始思惟方式是演繹性的,這就決定了咱們常常基於流程去思考問題,而面向對象建模偏偏須要的是概括性思惟。這也是着重須要自我訓練的地方。

咋作分界

模塊

首先須要劃分模塊。模塊(Module)是 DDD 中明確提到的分層前置手段,在工程實踐中,較爲常見的模塊策略有兩種:

技術職責分包:

  • client:富客戶端,thrift 接口劃分在這一層,亦用於將來組織前置緩存、業務無關的通用校驗等功能
  • gateway:網關層,業務鑑權、請求過濾,並將請求對內映射成統一的 event 事件
  • common:定義 core 和 client 所共用的內容
  • core:業務服務的實現
  • qatest:單元測試用例
  • web:https 請求接口、業務自測 curl 接口

業務職責分包:(就拿開放平臺 open 舉例)

  • open-base-client:兄弟系統的業務 rpc
  • open-erp-client:erp 開發者 rpc
  • open-pushrecord-client:推送訂單 rpc
  • open-web:外網請求

複雜系統建議採用技術分包策略,提升模塊代碼的複用性。

分層

而後咱們再來說模塊內的分層,這裏特指 core 模塊的內部分層。DDD 描述了幾個分層概念,分別是:防腐層、服務層、資源庫、領域對象、基礎層。

如代碼中所示,通常的工程中包的組織方式爲 {com. 公司名。組織架構。業務。上下文.*},這樣的組織結構可以明確的將一個上下文限定在包的內部。

  • com.open.adapter.* :防腐層(適配層,DO 轉義)
  • com.open.service.* :服務層(邏輯流轉層)
  • com.open.tair.* :資源庫(分佈式緩存、本地緩存)
  • com.open.dao.* :資源庫(關係型、非關係型)
  • com.open.domain.* :領域對象(DO)
  • com.open.util.* :基礎層(工具層)

這裏着重釋義一下防腐層,該層主要是將外部系統 DO 轉義成本系統 DO,避免外部 DO 一旦發生變化,本系統改動範圍過大的狀況,收斂影響面。

//接口層
  public class Consume {  
      public BaseResultDTO consumeMessage(OrderDetailMessage orderDetailMessage ) throws Exception, Throwable{
        OpenOrderDO openOrderDO = transferAdapter.orderDetailMessage2openOrderDO(orderDetailMessage);//調用防腐層
        openOrderService.handleOrderMeasage(openOrderDO);//調用服務層
      }
  }
  
  //防腐層
 public class TransferAdapter {
      public OpenOrderDO orderDetailMessage2openOrderDO(OrderDetailMessage orderDetailMessage){//轉換DO
        OpenOrderDO openOrderDO = new OpenOrderDO();
        openOrderDO.setTradeId(orderDetailMessage.getTradeId());
        openOrderDO.setOrderSource(orderDetailMessage.getOrderSource());
        openOrderDO.setBuyerUid(orderDetailMessage.getBuyerId());
        openOrderDO.setSellerUid(orderDetailMessage.getSellerId());
        return openOrderDO;
      }
  }

咋落地

落地就是模型到代碼的轉換,核心是保證模型和代碼的一致性。實際狀況下,因爲沒有好的保持模型和代碼一致的辦法,不少系統每每開始搞的不錯,慢慢就不一致了,也就逐漸爛掉了。

落地的方式,理論上有三種:

  1. 給出架構設計,開發人員負責落地
  2. 給出架構設計,給出所有實現
  3. 給出架構設計,給出核心代碼,開發人員去作擴展、實現

1 不可控,2 不現實。推薦 3 的作法:架構師給出設計方案,並給出骨幹實現,開發人員有了可類比的代碼,就可以比較準確的去作功能開發。這比空講要有效的多。

此外,落地中還要注意三點:

  1. 創建統一語言,避免認知誤差。客戶、產品經理、架構師、開發人員在需求理解上的誤差,是後期返工的一大來源
  2. 頻繁溝通,迭代共識。 因爲週期較短,即便發現了實現誤差,也能及時糾偏
  3. 快速反饋。開發人員模糊地帶主動詢問架構師,儘量保證模型與實現的一致

我的感悟

DDD 實際上是面向對象方法論的一個昇華。咱們回頭來看它,無外乎是經過劃分領域(聚合根、實體、值對象)、領域行爲封裝到領域對象(充血模式)、內外交互封裝到防腐層、職責封裝到對應的模塊和分層,從而實現了高內聚低耦合 —— 這也是它最精華的部分。

那麼 DDD 的各個概念重要麼?並不重要。概念掌握確實有助於提升咱們的業務思考能力,但 DDD 強調的是理論結合實踐,沒有通過實戰考驗,都是紙上談兵。經驗豐富的開發人員,即使沒有據說過 DDD,其思想也每每與 DDD 相符。我更建議的是:不要受限於對概念的掌握,而要透過層層表象思考它的本質,追求設計原則與實踐方法的融匯貫通。只有如此,才能針對不一樣的場景靈活地運用 DDD,而非生搬硬套。畢竟,設計老是如此,即便前人已經總結了許多的方法,也不能像數學公式那樣套用獲得準確無誤的結果。它不存在惟一的答案。

經驗有限,我對 DDD 的理解不免會有不足之處,歡迎你們共同探討,共同提升。

參考書籍

一、領域驅動設計精簡版二、實現領域驅動設計

相關文章
相關標籤/搜索