當今微服務盛行之架構師必經之路-領域驅動設計-下

itxiaoshen發表於2022-03-07

DDD架構

傳統分層架構

分層架構設計就是為了幫助我們達到高內聚、低耦合複用性設計和擴充套件性設計。整潔架構、CQRS、六邊形架構等微服務架構都旨在實現“高內聚低耦合”,而分層架構基本原則是每層只能與位於其下方的層發生耦合。分層架構又分為兩種:

  • 嚴格分層架構(Strict Layers Architecture),某層只能與其直接下層耦合。
  • 鬆散分層架構(Relaxed Layers Architecture),允許任意上層與任意下層耦合。由於使用者介面層和應用服務通常需要與基礎設施打交道,許多系統都是該架構。

DDD分層架構

DDD分層架構包含使用者介面層、應用層、領域層和基礎層;通過這些層次劃分,我們可以明確微服務各層的職能,劃定各領域物件的邊界,確定各領域物件的協作方式。DDD的分層架構中基礎層與使用者介面層、應用層和領域層都可能有關係,提供基礎能力給其他三層呼叫。

image-20220304141445226

  • 使用者介面層:顯示資訊給使用者,如對外的model、模型的轉換。一般包括使用者介面、Web 服務等,只處理使用者顯示和使用者請求,不應包含領域或業務邏輯。使用者介面層很重要,在於前後端呼叫的適配,Facade介面就起很好的作用,包括DO和DTO物件的組裝和轉換等。
  • 應用層:主要包含執行緒排程,應用服務,與模型進行與實體無關的業務邏輯。理論上不應有業務規則或邏輯,而主要是面向用例和流程相關的操作。
    • 應用層位於領域層之上,因為領域層包含多個聚合,所以它可協調 多個聚合服務和領域物件完成服務編排和組合 ,協作完成業務。
    • 應用層也是微服務間的互動通道,它可呼叫其它微服務,完成 微服務間的服務組合和編排
    • 開發設計時,不要將本該放在領域層的業務邏輯放到應用層。因為龐大的應用層會使領域模型失焦,時間一長微服務就會演化為傳統MVC三層架構,導致業務邏輯混亂。
    • 應用服務是在應用層,負責服務的組合、編排、轉發、轉換和傳遞,處理業務用例的執行順序以及結果的拼裝,以粗粒度服務通過API閘道器釋出到前端。還可進行安全認證、許可權校驗、事務控制、傳送或訂閱領域事件等。
  • 領域層:業務概念、規則、領域模型。主要包含聚合、聚合根、實體、值物件、領域服務等領域模型中的領域物件。
    • 聚合根:如果把聚合比作組織,聚合根則是組織的負責人,聚合根也叫做根實體,它不僅僅是實體,還是實體的管理者。
    • 聚合:高內聚低耦合,是領域模型中最底層的邊界,可以作為拆分微服務的最小單位,但是不建議單獨對應一個微服務,除非是對效能有極致要求的場景,一個微服務可以包含多個聚合,聚合之間的邊界是邏輯最天然的邊界,有了這個邏輯邊界,就可以在微服務拆分的時候作為拆分和組合依據,微服務架構演進也就不是難事了。
    • 聚合根的特點:聚合根是實體,具備唯一標識,有獨立的生命週期,一個聚合只有一個聚合根,聚合根在聚合之內採用引用依賴的方式對實體和值物件進行組織和協調,聚合根和聚合根之間通過唯一id進行聚合之間的協同;
    • 實體的特點:具備id標識,可以通過id進行相等性比較,實體在聚合內唯一,但是狀態可變,它依附於聚合根,它的生命週期由聚合根管理,實體一般都會持久化,跟資料持久化物件存在多種對應關係(一對一,一對多,多對一,1對0),實體可以引用聚合中的聚合根,實體,值物件;
    • 值物件特點:無id,不可變,無生命週期,用完即失效,值物件之間通過屬性值判斷相等性,他的核心是值,是一組概念完整的屬性集合,用於描述實體的特徵和狀態,值物件儘量只引用值物件;
    • 聚合設計步驟
      • 通過事件風暴(用例分析,場景分析,使用者旅程分析)得到實體和值物件,然後找出聚合根,按照高內聚低耦合的設計原則,找出跟聚合根緊密關聯的實體和值物件,即形成聚合,並畫出聚合內的實體和值物件的引用依賴關係,最後把業務把關聯緊密的聚合畫在同一個限界上線文中,即完成了領域建模。
    • 聚合設計原則
      • 聚合的設計原則: 高內聚,聚合儘量小,聚合之間通過id關聯,邊界之外使用最終一致性,在應用層實現跨聚合的呼叫。
    • 實現核心業務邏輯,通過各種校驗保證業務正確性。領域層主要體現領域模型的業務能力,它用來表達業務概念、業務狀態和業務規則。
    • 領域模型的業務邏輯主要由實體和領域服務實現:實體採用充血模型 實現所有與之相關的業務功能。
    • 實體和領域服務在實現業務邏輯上不是同級,當領域中的某些功能,單一實體或值物件無法實現,就會用到領域服務,它可組合聚合內的多個實體或值物件,實現複雜業務邏輯。
    • 領域服務:
  • 基礎層:是一個互動層次,為其它各層提供通用的技術基礎服務,包含三方工具、驅動、MQ、API閘道器、檔案、快取、DB、基礎服務等;最常用的還是提供DB持久化。
    • 基礎層包含基礎服務,它採用依賴反轉,封裝基礎資源服務,實現應用層、領域層與基礎層解耦。
    • 傳統架構由於上層應用對DB強耦合,在架構演進最怕換DB,一旦更換帶來工作量較大。但採用依賴反轉,應用層即可通過解耦保持獨立核心業務邏輯。當DB變更,只需更換DB基礎服務。
    • 防腐層:用於應對業務的變化,形成抽象業務,例如抽象MQ基礎設施層防止第三方元件的變化比如從Kafka更換為Pulsar
    • 倉庫:為了解耦領域邏輯和資料處理邏輯,在中間加了薄薄的一層倉儲。倉儲模式包含倉儲介面和倉儲實現,倉儲介面面向領域層提供基礎層資料處理相關的介面,倉儲實現則完成倉儲介面對應的資料持久化相關的邏輯處理。一個聚合配備一個倉儲,由倉儲完成聚合資料的持久化。領域層邏輯面向倉儲介面程式設計,聚合內的資料持久化過程為DO(領域物件)轉PO(持久化物件)。當需要更換資料庫型別,或者更改資料處理邏輯時,我們就可以保持業務邏輯介面不動,只修改倉儲實現,保證了領域層業務邏輯的乾淨和純潔。後面基礎層發生了變化,則領域層無需動任何程式碼,只要倉儲介面不變,領域層的邏輯就可以一直保持不變,維護了領域層的穩定性。領域服務是可以做成企業級可複用的服務的,因此穩定性必須有保障。
    • 工廠:DO物件建立時,需要確保聚合根和它依賴的物件同時被建立,如果這項工作交給聚合根來實現,則聚合根的建構函式將變得異常龐大,所以我們把通用的初始化DO的邏輯,放到工廠中去實現,通過工廠模式封裝聚合內複雜物件的建立過程,完成聚合根,實體和值物件的建立。DO物件建立時,通過倉儲從資料庫中獲取PO物件,通過工廠完成PO到DO的轉換,工廠中還可以包含DO到PO物件的轉換過程,方便完成資料的持久化。

DDD四層架構規範

  • 領域中的物件由實體和值物件組成;對值物件的訪問必須經由其所屬的實體物件。
  • 相關聯的一組實體和值物件組成聚合;對聚合內的物件的訪問必須經由聚合根物件。
  • 跨實體的操作必須經由領域服務。
  • 應用服務層只通過領域服務或聚合根來組織業務,自身不帶任何實現邏輯。
  • 業務和資料隔離,領域層只關注業務,資料支撐全部交由基礎設施層。

DDD架構和MVC架構

  • MVC架構,目前典型實現包括SpringMVC,Spring Boot,固化業務,是一種結構性設計模式,也是一種面向資料的設計。

image-20220306102706707

  • 行為型設計模式:DCI架構,物件導向程式設計新構想,對於MVC模式的補充,物件導向量化的落地。包括Data(物件資料),Context(物件場景)、InterActions(互動行為)。DCI架構模式是架構層面的方法論,而四色建模法則是需求層面的方法論。

image-20220306115149271

  • 從DDD的角度看MVC架構的問題
    • 程式碼角度
      • 瘦實體模型:只起到資料類的作用,業務邏輯散落到service,可維護性越來越差;
      • 面向資料庫表程式設計,而非模型程式設計;
      • 實體類之間的關係是複雜的網狀結構,成為大泥球,牽一髮而動全身,導致不敢輕易改程式碼;
      • service類承接的所有的業務邏輯,越來越臃腫,很容易出現幾千行的service類;
      • 對外介面直接暴露實體模型,導致不必要開放,內部邏輯對外暴露,就算有DTO類一般也是實體類的直接copy;
      • 外部依賴層直接從service層呼叫,欄位轉換、異常處理大量充斥在service方法中;
      • 控制器冗餘,控制器對於物件依賴過重,物件之間會產生耦合。由於Spring的存在,其實我們的開發是不符合物件導向的。
    • 專案管理角度:
      • 交付效率:越來越低;
      • 穩定性差:不好測試,程式碼改動的影響範圍不好預估;
      • 理解成本高:新成員介入成本高,長期會導致模組只有一個人最熟悉,離職成本很大;
  • MVC架構到DDD分層架構的對映
    • DDD優點
      • 業務邏輯清晰、業務人員也可以讀。
      • 業務穩定度,業務不動,程式碼不動。
      • 防腐層隔離變化。
      • 各領域內自治,可以自我發展。
      • 用倉庫來管理物件的儲存,倉庫中整合工廠Factoty/Builder應對複雜物件的組裝。

image-20220304165111447

DDD專案包結構

頂級包目錄下DDD四層目錄如下:

image-20220306190750201

image-20220306190836685

總結

  • 微服務的拆分第一個層面就是資料庫層面的拆分,第二層面就是上層應用功能業務層面的拆分。但如果系統上層邏輯是依賴底層一個大的資料資源,那麼微服務拆分不當就有可能導致拆分後的微服務出現大量的跨庫查詢、分散式事務的情況。基於DDD的思路去做微服務拆分,包括進行事件風暴。

    • 把相關的核心業務事件全部梳理出來,找到關鍵的物件基於物件做聚合,做完之後就可以梳理出一個關鍵內容叫限界上下文,而限界上下文是微服務拆分一個很關鍵的點,但這也可能導致後續微服務拆分過細帶來相關問題。命令、操作、物件、物件狀態。
  • DDD切入點:理解領域、拆分領域、細化領域

    • 理解領域知識是基礎。這裡要強調領域專家的重要性。因為領域專家對領域內的各種業務場景和各種業務規則非常清楚,哪些是核心業務關注點,靠的是沉澱領域內的各種知識。
    • 拆分領域:領域建模的基礎是要先理解領域,讓自己成為領域專家。有時一個領域往往太複雜,涉及到的領域概念、業務規則、互動流程太多,需要將領域進行拆分,本質上就是把大問題拆分為小問題,然後各個擊破的思路。然後既然把一個大的領域劃分為了多個小的領域(子域),那最關鍵的就是要理清每個子域的邊界;然後要搞清楚哪些子域是核心子域,哪些是非核心子域,哪些是公共支撐子域;然後還要思考子域之間的聯絡是什麼。
    • 細化子域
      • 梳理領域概念:梳理出領域內我們關注的概念、概念的關係,並統一交流詞彙,形成統一語言;
      • 梳理業務規則:梳理出領域內我們關注的各種業務規則,DDD中叫不變性(invariants),比如唯一性規則,餘額不能小於零等;
      • 梳理業務場景:梳理出領域內的核心業務場景,比如電商平臺中的加入購物車、提交訂單、發起付款等核心業務場景;
      • 梳理業務流程:梳理出領域內的關鍵業務流程,比如訂單處理流程,退款流程等;
  • 領域就是問題域,有邊界,領域中有很多問題;

  • 任何一個系統要解決的那個大問題都對應一個領域;

  • 通過建立領域模型來解決領域中的核心問題,模型驅動的思想;

  • 領域建模的目標針對我們在領域中所關心的問題,即只針對核心關注點,而不是整個領域中的所有問題;

  • 領域模型在設計時應考慮一定的抽象性、通用性,以及複用價值;

  • 通過領域模型驅動程式碼的實現,確保程式碼讓領域模型落地,程式碼最終能解決問題;

  • 領域模型是系統的核心,是領域內的業務的直接沉澱,具有非常大的業務價值;

  • 技術架構設計或資料儲存等是在領域模型的外圍,幫助領域模型進行落地;

  • DDD架構作為一套先進的方法論,在很多場景能發揮很大價值,高階的架構師把DDD架構當成一種工具,結合其他架構經驗一起為業務服務,但是DDD也存在一些不足

    • 效能:DDD是基於聚合來組織程式碼,對於高效能場景下,載入聚合中大量的無用欄位會嚴重影響效能,比如報表場景中,直接寫SQL會更簡單直接;
    • 事務:DDD中的事務被限定在限界上下文中,跨多個限界上下文的場景需要開發者額外考慮分散式事務問題;
    • 難度係數高,推廣成本大:DDD專案需要領域專家專家,且需要特別熟悉業務、建模、OOP,對於管理者來說評估一個人是否真的能勝任也是一件困難的事情;

**本人部落格網站 **IT小神 www.itxiaoshen.com

相關文章