首页
/
每日頭條
/
科技
/
ddd分層架構實例
ddd分層架構實例
更新时间:2024-04-30 00:06:42
1.前言

最近我發現團隊項目中的某個應用複雜度越來越高,具體表現為:

  • 代碼可讀性較差:各個服務之間調用複雜,流程不清晰
  • 修改部分業務導緻大量測試用例失敗,但很難快速的尋找出這些測試用例失敗的根因

基于這些情況,我開始尋找降低複雜度的方案,于是就有了這篇再談DDD的文章。

1.1 具體問題1.1.1 宏觀角度

從宏觀來說,軟件架構模式演進經曆了三個階段。

ddd分層架構實例(談談代碼降低複雜度)1

  • 第一階段是單機架構:采用面向過程的設計方法,系統包括客戶端 UI 層和數據庫兩層,采用 C/S 架構模式,整個系統圍繞數據庫驅動設計和開發,并且總是從設計數據庫和字段開始。
  • 第二階段是集中式架構:采用面向對象的設計方法,系統包括業務接入層、業務邏輯層和數據庫層,采用經典的三層架構,也有部分應用采用傳統的 SOA 架構。這種架構容易使系統變得臃腫,可擴展性和彈性伸縮性差。
  • 第三階段是分布式微服務架構:随着微服務架構理念的提出,集中式架構正向分布式微服務架構演進。微服務架構可以很好地實現應用之間的解耦,解決單體應用擴展性和彈性伸縮能力不足的問題。我們知道,在單機和集中式架構時代,系統分析、設計和開發往往是獨立、分階段割裂進行的。

比如,在系統建設過程中,我們經常會看到這樣的情形:A 負責提出需求,B 負責需求分析,C 負責系統設計,D 負責代碼實現,這樣的流程很長,經手的人也很多,很容易導緻信息丢失。最後,就很容易導緻需求、設計與代碼實現的不一緻,往往到了軟件上線後,我們才發現很多功能并不是自己想要的,或者做出來的功能跟自己提出的需求偏差太大。

而且在單機和集中式架構這兩種模式下,軟件無法快速響應需求和業務的迅速變化,最終錯失發展良機。此時,分布式微服務的出現就有點恰逢其時的意思了。

上面這部分來自于極客時間,這裡面指出一般DDD是使用在微服務設計與拆分上,但我認為在單體應用中做模塊的拆分也是可以并推薦的,這可以讓你的模塊在需要時可以即刻拆分出去——變成一個獨立的微服務。相關可以參考【ZStack】4.進程内服務,這是一個開源,并實施于生産中很好的一個案例。

1.1.2 微觀角度

這個問題很簡單,service的代碼必然會越堆越多,而且聚攏越來越多的業務。

ddd分層架構實例(談談代碼降低複雜度)2

image

2.DDD入門

我們先來看一張圖:

ddd分層架構實例(談談代碼降低複雜度)3

從最外層開始——什麼是領域?大白話來說就是一系列問題的聚合。舉個例子:

  • 電商平台中的電商域,你要解決的一系列問題有:
    • 用戶認證
    • 移動收付
    • 訂單
    • 報價
    • ...

可以看到,域是呈現出來的是一系列的業務領域問題。

在不同域中,同一個數據實體的抽象形态往往是不同的。比如,Bookstore 應用中的書本,在銷售領域中關注的是價格,在倉儲領域中關注的是庫存數量,在商品展示領域中關注的是書籍的介紹信息。

2.1 上下文邊界

往裡面,我們應該看到的是限界上下文。其實這個翻譯并不好,原文叫bounded context,叫做上下文邊界更為妥當。本質上來說,它定義了邊界。再具體點,即:用來封裝通用語言和領域對象,提供上下文環境,保證在領域之内的一些術語、業務相關對象等(通用語言)有一個确切的含義,沒有二義性。

2.2 聚合

接下來,我們看到了聚合。聚合就是由業務和邏輯緊密關聯的實體和值對象組合而成的,聚合是數據修改和持久化的基本單元,每一個聚合對應一個倉儲,實現數據的持久化。

聚合有一個聚合根和上下文邊界,這個邊界根據業務單一職責和高内聚原則,定義了聚合内部應該包含哪些實體和值對象,而聚合之間的邊界是松耦合的。按照這種方式設計出來的微服務很自然就是“高内聚、低耦合”的。

那聚合根是什麼呢?

聚合根的主要目的是為了避免由于複雜數據模型缺少統一的業務規則控制,而導緻聚合、實體之間數據不一緻性的問題。

傳統數據模型中的每一個實體都是對等的,如果任由實體進行無控制地調用和數據修改,很可能會導緻實體之間數據邏輯的不一緻。而如果采用鎖的方式則會增加軟件的複雜度,也會降低系統的性能。

如果把聚合比作組織,那聚合根就是這個組織的負責人。聚合根也稱為根實體,它不僅是實體,還是聚合的管理者。

首先它作為實體本身,擁有實體的屬性和業務行為,實現自身的業務邏輯。

其次它作為聚合的管理者,在聚合内部負責協調實體和值對象按照固定的業務規則協同完成共同的業務邏輯。

最後在聚合之間,它還是聚合對外的接口人,以聚合根 ID 關聯的方式接受外部任務和請求,在上下文内實現聚合之間的業務協同。也就是說,聚合之間通過聚合根 ID 關聯引用,如果需要訪問其它聚合的實體,就要先訪問聚合根,再導航到聚合内部實體,外部對象不能直接訪問聚合内實體。

2.3 實體與值對象

在 DDD 中有這樣一類對象,它們擁有唯一标識符,且标識符在曆經各種狀态變更後仍能保持一緻。對這些對象而言,重要的不是其屬性,而是其延續性和标識,對象的延續性和标識會跨越甚至超出軟件的生命周期。我們把這樣的對象稱為實體。其實很像數據庫裡自帶不變id的一行行業務數據。

值對象相對不是那麼重要,因為它是用來描述實體的一組屬性集。很多系統中的實現會以json來實現,比如【ZStack】7.标簽系統。

為了方便理解,這邊做個小結。實體和值對象的目的都是抽象聚合若幹屬性以簡化設計和溝通,有了這一層抽象,我們在使用人員實體時,不會産生歧義,在引用地址值對象時,不用列舉其全部屬性,在同一個限界上下文中,大幅降低誤解、縮小偏差,兩者的區别如下:

  1. 兩者都經過屬性聚類形成,實體有唯一性,值對象沒有。在本文案例的限界上下文中,人員有唯一性,一旦某個人員被系統納入管理,它就被賦予了在事件、流程和操作中被唯一識别的能力,而值對象沒有也不必具備唯一性。
  2. 實體着重唯一性和延續性,不在意屬性的變化,屬性全變了,它還是原來那個它;值對象着重描述性,對屬性的變化很敏感,屬性變了,它就不是那個它了(意味着不可變性,它可能是從外部查詢來的)。
  3. 戰略上的思考框架穩定不變,戰術上的模型設計卻靈活多變,實體和值對象也有可能随着系統業務關注點的不同而更換位置。比如,如果換一個特殊的限界上下文,這個上下文更關注地址,而不那麼關注與這個地址産生聯系的人員,那麼就應該把地址設計成實體,而把人員設計成值對象。
3. DDD上手3.1 從三層模型到DDD

這裡先簡單介紹一下三層模型到DDD對應的一個變化。

ddd分層架構實例(談談代碼降低複雜度)4

可以的看得出來,主要是對service進行了拆分。一般可以拆成三層:

  • 應用服務層:多個領域服務或外部應用服務進行封裝、編排和組合,對外提供粗粒度的服務。應用服務主要實現服務組合和編排,是一段獨立的業務邏輯。
  • 領域服務層:由多個實體組合而成,一個方法可能會跨實體進行調用。在代碼過于複雜的時候,可以将每個領域服務拆分為一個領域服務類,而不是将所有領域服務代碼放到一個領域服務類中。
  • 實體:是一個充血模型。同一個實體相關的邏輯都在實體類代碼中實現。
3.2 建模簡介

我們可以用三步來劃定領域模型和微服務的邊界。

  • 第一步:在事件風暴中梳理業務過程中的用戶操作、事件以及外部依賴關系等,根據這些要素梳理出領域實體等領域對象。
  • 第二步:根據領域實體之間的業務關聯性,将業務緊密相關的實體進行組合形成聚合,同時确定聚合中的聚合根、值對象和實體。在第二章的圖裡,聚合之間的邊界是第一層邊界,它們在同一個微服務實例中運行,這個邊界是邏輯邊界,所以用虛線表示。
  • 第三步:根據業務及語義邊界等因素,将一個或者多個聚合劃定在一個限界上下文内,形成領域模型。在第二章的圖裡,限界上下文之間的邊界是第二層邊界,這一層邊界可能就是未來微服務的邊界,不同限界上下文内的領域邏輯被隔離在不同的微服務實例中運行,物理上相互隔離,所以是物理邊界,邊界之間用實線來表示。
3.3 實踐:設計一個MiniStack

為了便于大家理解,我在這裡會設計一個很簡單的Iaas平台,并在裡面代入最基本的DDD概念。

3.3.1 産品願景
  • 為了:企業的内部的開發者、運維人員
  • 他們的:計算、存儲、網絡資源管理
  • 這個:MiniStack
  • 是一個:私有雲平台
  • 它可以:管理計算、存儲、網絡資源管理,幫用戶簡單快速的創建虛拟機
  • 而不像:OpenStack
  • 我們的産品:簡單、健壯、智能

串起來就是:為了滿足企業的内部的開發者和運維人員,他們的硬件資源管理,我們建設裡這個MiniStack,它是一個私有雲平台,它可以管理計算、存儲、網絡資源管理,幫用戶簡單快速的創建虛拟機,而不像OpenStack,我們的産品簡單、健壯、彈性。

3.3.2 場景分析

因篇幅原因,我們來聊個最典型的場景——創建虛拟機,以便理出相關的領域模型。

在這裡我們需要注意,我們要盡可能地梳理整個系統發生的操作、命令、領域時間以及依賴變化等。

3.3.2.1 創建虛拟機
  1. 用戶登陸系統:從數據庫中對信息進行校驗,完成登陸認證
  2. 創建虛拟機:填寫虛拟機名、集群、計算規格、L3網絡以及鏡像。如果需要的話(簡單的體現),可以指定所在的物理機、以及網段。
  3. VM服務需要提供創建虛拟機接口
  4. 提交至MiniStack引擎,引起開始做相關調度:
    1. 尋找符合計算、存儲資源的低負載物理機,并更新vm所屬的物理機
    2. 物理機服務需要提供查詢接口
    3. 分配L3網絡中的空閑IP,并更新vm相關的網絡信息
    4. 網絡服務需要提供IP分配接口
    5. 告訴物理機agent:從鏡像服務器拉取鏡像到第1步尋找出的物理機
    6. 物理機服務需要提供拉取鏡像接口
    7. 告訴物理機agent啟動參數,拉起vm
    8. VM服務需要提供啟動接口
  5. 界面上返回創建成功,用戶可以看到vm

但創建完虛拟機以後并不是就這麼完事了,萬一哪天這台物理機carsh了呢?哪天CPU因為奇怪的進程而打滿了呢?因此為了我們的目标——智能,創建vm後,MiniStac每5分鐘收集一系列的監控信息:

  1. 向物理機agent發送心跳包,确保物理機狀态正常
  2. 向虛拟機agent發送心跳包,并會返回:計算、存儲、網絡的相關狀态
3.3.3 宏觀設計:領域建模

在這一步,我們需要對業務進行分析,建立領域模型。一般步驟為:

  1. 找出領域實體和值對象等領域對象
  2. 找出聚合根,根據實體、值對象與聚合根的依賴關系,建立聚合
  3. 第三步根據業務及語義邊界等因素,定義限界上下文
3.3.3.1 定義實體

我們大緻可以找出幾個實體:

  • 虛拟機
    • 啟動
    • 停止
  • 物理機的存儲資源
    • 查詢
    • 分配
    • 釋放
  • 物理機的計算資源
    • 查詢
    • 分配
    • 釋放
  • L3網絡
    • 分配IP
  • 鏡像服務器
    • 查詢鏡像
    • 添加鏡像
    • 發布鏡像
3.3.3.2 定義聚合與限界上下文

在找聚合前,我們先要找出聚合根。可以分為物理機、網絡、鏡像服務器、虛拟機。而他們彼此都是獨立的上下文,在需要的情況下,也可以拆成一個個微服務,如果是單體應用,則建議用模塊手段進行邏輯隔離。

ddd分層架構實例(談談代碼降低複雜度)5

3.3.4 微觀:領域對象與代碼結構分析

當我們完成宏觀上的建模後,便可以開始做微觀的事:梳理微服務内的領域對象,梳理領域對象之間的關系,确定它們在代碼模型和分層架構中的位置,建立領域模型與微服務模型的映射關系,以及服務之間的依賴關系。

大緻上,分為兩步:

  1. 分析領域對象
  2. 設計代碼結構
3.3.4.1 分析領域對象

在這一步,我們需要确認:

  • 服務的分層
  • 應用服務由哪些服務組成
  • 領域服務包含哪些實體和實體方法
  • 哪個實體是聚合根
  • 實體有哪些屬性和方法
  • 哪些對象為值對象

由于我們的用例比較簡單,整理如下:

  • 應用服務:
    • VM創建服務:負責創建VM,會調度大量的底層領域服務
  • 領域服務:VM服務、物理機服務、網絡服務、鏡像服務
    • VM服務:管理VM的生命周期,如創建、删除、啟動、停止等
    • 物理機服務:物理機相關服務,如添加、删除、狀态變更、心跳感知、資源RUD等
    • 網絡服務:網絡相關服務,如創建删除L2、L3網絡,IP管理等
    • 鏡像服務:鏡像服務器相關服務,如添加、删除、狀态變更、增加鏡像等
  • 實體:VM實體、物理機實體、本地存儲實體(物理機存儲)
    • VM實體:啟動、停止等
    • 物理機實體:狀态變更、心跳感知等
    • L3實體:IP段添加、删除、IP分配、釋放等
    • 本地存儲實體:存儲的占用與釋放

ddd分層架構實例(談談代碼降低複雜度)6

接下來看一下聚合中的對象,我們把幾個聚合根識别出來:

  • 物理機聚合的中的聚合根是物理機
  • 網絡聚合中的聚合根是L2網絡
  • 鏡像聚合中的聚合根是鏡像服務器
  • 虛拟機聚合中的聚合根是虛拟機實體

而上面提到的實體屬性與方法我們已經在圖中呈現出來了。

關于值對象,可以參考【ZStack】7.标簽系統。該設計用于真實生産中。

3.3.4.2 設計代碼結構

當我們完成領域對象的分析後,我們便開始設計各領域對象在代碼模型中的呈現方式了——即建立領域對象與代碼對象的映射關系。根據這種映射關系,服務人員可以快速定位到業務邏輯所在的代碼位置。

宏觀上,我們可以參考以下分層模型:

ddd分層架構實例(談談代碼降低複雜度)7

微觀實施上,我們可以參考COLA。

4.小結

本文和大家一起捋了一遍DDD,并在文裡“憑空的”設計了一個項目。其實這個項目并非憑空,我參考了以前參與的開源項目ZStack并對它做出了簡化——該項目目前跑在大量的企業用戶的私有雲中,疊代已有6年多。因此無論從設計還是落地來說,都有一定的參考經驗。

為了大家方便将文中的例子結合ZStack代碼理解,我這邊做了一個映射。

ddd分層架構實例(談談代碼降低複雜度)8

,
Comments
Welcome to tft每日頭條 comments! Please keep conversations courteous and on-topic. To fosterproductive and respectful conversations, you may see comments from our Community Managers.
Sign up to post
Sort by
Show More Comments
推荐阅读
lols7賽事開幕
lols7賽事開幕
相信各位資深撸友們都曾遇到過這種情況,一不小心就五殺順便帶領全隊翻盤拯救世界了,這原本是一個在朋友圈炫耀的絕佳時刻,但偏偏沒有用軟件這這局比賽錄制下來,隻能讓這次亮眼操作成為泡影。同時,相信不少玩家也像小編那樣很讨厭LOL國服自帶的視頻錄制...
2024-04-30
釣魚用拉餌一般用什麼型号的魚鈎拉餌好拉啊
釣魚用拉餌一般用什麼型号的魚鈎拉餌好拉啊
釣魚用拉餌一般用什麼型号的魚鈎拉餌好拉啊?釣魚一般用2号或者3号的魚鈎拉餌,今天小編就來聊一聊關于釣魚用拉餌一般用什麼型号的魚鈎拉餌好拉啊?接下來我們就一起去研究一下吧!釣魚用拉餌一般用什麼型号的魚鈎拉餌好拉啊釣魚一般用2号或者3号的魚鈎拉...
2024-04-30
華為手機自動重啟是什麼原因
華為手機自動重啟是什麼原因
華為手機自動重啟是什麼原因?安裝的軟件有沖突,可以檢查一下最近安裝的軟件,是不是一運行此軟件就自動重啟,如果是這樣,可以把該軟件卸載,我來為大家講解一下關于華為手機自動重啟是什麼原因?跟着小編一起來看一看吧!華為手機自動重啟是什麼原因安裝的...
2024-04-30
高德地圖車機版6.0體驗
高德地圖車機版6.0體驗
[愛卡汽車科技頻道原創]作為未來出行生活的兩個重要屏幕,手機和車機的較量一直沒有停歇。現在大多數人開車更喜歡使用手機導航而非車機導航,原因就是車機導航查找麻煩、道路更新不及時、指示不清晰、沒有實時路況、更新慢等等。為了一改往日的形象,高德公...
2024-04-30
電腦斷網出現感歎号是什麼意思
電腦斷網出現感歎号是什麼意思
電腦斷網出現感歎号是什麼意思?出現感歎号本地連接受限制或無連接的标志,今天小編就來說說關于電腦斷網出現感歎号是什麼意思?下面更多詳細答案一起來看看吧!電腦斷網出現感歎号是什麼意思出現感歎号本地連接受限制或無連接的标志。是檢查路由器與寬帶接入...
2024-04-30
Copyright 2023-2024 - www.tftnews.com All Rights Reserved