DDD——從業務架構與到部署架構

背景

最近一直在整理業務架構,之前同一份代碼邏輯,我們最多的邏輯會存在在 4 個 Git 倉庫,一些業務邏輯修改,經常需要同時修改多個地方;同時,同一 Git 倉庫中,一個代碼邏輯也有多處判斷的地方。

在此之前,我們已經劃分了業務模塊,但是尚未完全重構完成,因此,我在之前的基礎上,繼續用 DDD 重構代碼。

原架構

原有的架構,我們存在多個系統,每個系統都是直接連接數據庫,需要使用哪些表,都是直接用。這也就造成了我們業務邏輯存在多處的原因。

DDD 介紹

DDD 常見的分層架構如下:

首先聲明一個個人觀點DDD 是業務設計模式,是技術要賦能業務體現,裏面的每一層都是和業務相關的,即使是基礎設施層,也是業務需要定製化使用的一部分。如果是和業務無關的或者是跨業務使用的,可以單獨放置到一個 Common 模塊中。

例如,僅存放 Redis 的配置信息,對於 Redis 與業務無關或跨業務使用的 GET、SET 等命令的封裝,可以放置在在一個 Common 項目中,被基礎設施層引用。

用戶接口層

簡介

“用戶”接口層負責將 “用戶” 的輸入轉換爲對應用程序層的調用,並將結果呈現給 “用戶”。這裏“用戶” 指調用方(客戶端)。

職責

用戶接口層常見的職責有以下內容:

  1. 限流配置

  2. 統一鑑權

  3. 緩存

  4. 所有異常的捕獲

當然,這些也能前置到應用網關中。

應用層

簡介

應用層主要是面向業務場景,用於編排(Orchestration)業務流程。

名詞解釋

  1. 應用服務

應用服務主要負責處理用戶的請求,協調領域模型(如實體、值對象、領域服務等)的使用,並將結果返回給調用者。

它是領域邏輯與外部系統(如用戶界面、API)的橋樑。

  1. 集成事件

集成事件在多個系統或微服務之間傳遞信息,以確保不同系統之間的一致性。集成事件是大多是跨系統、不在同一個進程、異步的

職責

  1. 基礎驗證

主要驗證數據格式符合要求,例如數據不能爲空、郵箱地址格式正確、編輯數據的時候 id > 0 等。這種驗證通常是爲了防止無效的數據進入系統,提高系統的健壯性。

  1. 協調業務流程

協調多個聚合的服務和領域服務完成業務編排和組合,協作完成業務操作;調用其它微服務的應用服務,完成微服務之間的服務組合和編排。

  1. 應用程序層驗證:

如果驗證邏輯涉及到跨多個領域實體或聚合時(前提條件),需要在應用層驗證(業務邏輯在領域層,業務流程在應用層)。常見場景:某一個步驟處理失敗了,拋出異常,中斷流程;處理成功了,繼續執行後續步驟。

常見套路

  1. 使用 Validation 將基礎驗證前置

  2. 準備數據 包括從外部服務或持久化源取出相對應的 Entity、VO 以及外部服務返回的 DTO。

  3. 執行操作 包括新對象的創建、賦值,以及調用領域對象的方法對其進行操作。需要注意的是這個時候通常都是純內存操作,非持久化。

  4. 持久化 將操作結果持久化,或操作外部系統產生相應的影響,包括髮消息等異步操作。

規範要求

  1. 業務流程在應用層,業務邏輯在領域層。也就是基本不會有 if-else 分支邏輯(但是可以有 if-throw 的業務流程),也不要有任何計算。

  2. ApplicationService 不對異常做任何處理,所有異常的捕獲放置在用戶接口層。

  3. ApplicationService 的如參爲 CQE(command、query、event)對象,出參是 DTO。

  4. 避免複用 CQE

領域層

簡介

領域層主要體現領域模型的業務能力,它用來表達業務概念、業務狀態和業務規則。

領域層包含聚合根、聚合、實體、值對象、領域服務等領域模型中的領域對象。

名詞解釋

  1. 聚合根、聚合、實體、值對象

    a. 實體: 實體是具有唯一標識的對象,其特徵是可以通過標識符進行區分,即使兩個實體的屬性完全相同,它們依然是不同的實體。通俗的介紹,一個實體就對應一個表。b. 值對象: 值對象是沒有唯一標識的對象,其意義完全依賴於屬性值的組合。不同於實體,值對象是不可變的。c. 聚合: 聚合是一個或多個關聯的實體和值對象的集合,這些實體和對象必須作爲一個整體進行一致性操作。聚合以聚合根爲唯一入口,對外暴露行爲和屬性。主要作用是將相關聯的對象組合在一起,保證它們的狀態和行爲的一致性,避免了不必要的外部依賴,增強了模型的內聚性。d. 聚合根: 聚合根是一個聚合(Aggregate)的入口,它是聚合內部所有實體的唯一標識。聚合根是一個特殊的實體,它對外提供接口,在對聚合進行操作時,外部只能通過聚合根來訪問和修改聚合內部的其他對象,從而保證數據的一致性。此外,每個事務應該只涉及一個聚合根,多個聚合根之間是松耦合,需要在應用層保證最終一致性的。

  2. 領域服務

領域服務是領域模型的一部分,主要用於處理那些不能簡單地歸屬於某一個實體或聚合根的業務行爲。

理想情況下我們希望所有的業務邏輯都發生在聚合根之中,但是,理想和現實始終是存在差距的,在有些情況下將業務邏輯放到聚合根中並不合適,例如對多個聚合根進行排序、判斷用戶名是否有重複等,此時我們需要將這些邏輯放置在領域服務中。

此外,在貧血模型中,聚合根中不存在任何業務,這些業務邏輯就需要在領域服務中實現。

  1. 領域事件

領域事件是指在領域中發生的、希望同一域(進程內)的其他部分知道的事情。領域事件的一個重要好處是可以明確地表達副作用。

和消息隊列中間件不同的是,領域事件通常是立即執行的、在同一個進程內、可能是同步或異步。領域事件可能觸發集成事件。

職責

  1. 業務規則驗證

領域層負責業務規則的驗證。這些驗證是業務邏輯的一部分,確保數據滿足特定的業務要求,比如判斷用戶是否有權限執行某個操作、產品名是否重複等。

領域層的驗證是 DDD 中的核心,因爲它直接關係到業務規則的正確性和業務模型的完整性。

  1. 業務邏輯的表達與封裝

域層定義並強制執行業務規則,確保系統的行爲符合業務邏輯。此外,領域層中的實體負責維護和管理業務對象的狀態,並確保這些狀態的變化符合業務規則。

基礎設施層

簡介

爲其它各層提供通用的技術和基礎服務,包括第三方工具、驅動、消息中間件、網關、文件、緩存以及數據庫等。比較常見的功能是提供數據庫持久化。

基礎層包含基礎服務,它採用依賴倒置設計(實現領域層或者應用層的接口),封裝基礎資源服務,實現應用層、領域層與基礎層的解耦,降低外部資源變化對應用的影響。

常見職責

  1. 倉儲層

基礎設施層最常見的是倉儲層,用於實現數據庫的持久化。倉儲層的接口放置在領域層,實現放置在基礎設施層。每一個 Repository 對應一個聚合根,Repository 用於對聚合根的增刪改查,同時封裝聚合根內部各個實體間關聯的細節。

  1. 防腐層(ACL)

將領域模型與外部系統隔離,ACL 可以更輕鬆地維護領域模型並使其適應外部環境的變化,提高可維護性、可測試性,並易於監控與可觀測。防腐層的接口放置在領域層,實現放置在基礎設施層。

倉儲層其實也是 ACL 中的一部分,只是比較重要,常被單拎出來。

微服務

概念介紹

微服務的概念源自領域驅動設計(DDD)中的限界上下文(BC)模式。DDD 通過將大型業務模型劃分爲多個 BC 並明確其邊界來處理大型模型。每個 BC 必須有自己的模型和數據庫;同樣,每個微服務都擁有其相關數據。此外,每個 BC 通常都有自己的通用語言,以幫助軟件開發人員和領域專家之間的溝通。

微服務(與下面的 業務微服務 是一個概念)是一種邏輯架構,它可能與物理架構一致,也可能不一致。

我看了很多講 DDD 或者微服務的文章,大多都只講述了 DDD 的概念,對於應該怎麼部署(物理架構),並沒有多餘的描述,這裏簡單介紹下幾種部署形式。

邏輯架構與物理架構

邏輯架構或者稱爲功能架構,將一個系統劃分爲多個模塊,每個模塊都是邏輯架構中的一項。一般情況下,邏輯架構是與業務微服務是一致的。

物理架構指各個業務微服務(邏輯架構)在時間部署運行時的架構,也可以稱爲 部署架構。物理架構與邏輯架構可能一致,也可能不一致。

部署架構

同一個業務微服務,我們可以部署爲一個 Application(App),也可以部署爲多個 App,兩者之間通過 RPC 通信調用。

垂直切片

水平切片

前者與業務邏輯架構一致,稱爲垂直切片(Vertical Slice);後者按照代碼職責部署,稱爲水平切片(Horizontal Slice)。

水平切片、垂直切片都是指物理架構。

垂直切片

將每個業務微服務,單獨部署爲一個應用。垂直切片架構中,通過功能而不是層來組織應用程序,每個功能都封裝在自己的 “切片” 中,包含從表示層到數據訪問層的所有必要組件。

水平切片

按照職責分層,例如 DDD 中(UI、Application、Domain、InfraStructure 四層)或者傳統的三層模型(UI、BLL、DAL),將其中的每一層或者任意兩層組合,然後部署爲一個應用。同一個業務邏輯會分散在多個應用之間,應用之間通過 RPC 或者 HTTP 進行調用,才能完整的實現一次請求的業務邏輯表達。

對比

業務一致性

垂直切片:優

  • 能更好的保持業務一致性和完整性,降低系統的複雜性。

  • 所有業務邏輯、相關功能和數據模型都在一個服務。

水平切片:良

  • 業務邏輯會分散在多個進程中。

業務隔離性

垂直切片:優

  • 隔離功能,減少應用程序不同部分之間的耦合。

水平切片:良

  • 不同業務代碼耦合在一個進程,一個業務存在問題,可能也會影響其他業務。

  • 全部業務的業務邏輯更加集中,便於維護和管理。

  • 如果每一個業務都單獨部署 RPC,成本會比較高。

業務交付與部署

垂直切片:優

  • 業務微服務相互獨立,耦合更低,不同功能代碼耦合更少,對交付更加友好

  • 垂直切片架構可以帶來更加模塊化和獨立的部署策略,減少變更的影響並使維護更易於管理。

  • 新增業務,不會影響已有的程序重新發版,每個業務需求都可以單獨交付。

  • 一個業務存在問題,可能也會影響其他業務。

  • 垂直切片架構允許通過一次遷移一個切片的功能來進行增量重構,從而降低風險並允許逐步改進。

  • 排查問題不用跨應用,效率高。

  • 成本低

水平切片:良

  • 業務存在耦合,不利於單獨交付。單個功能的代碼通常分佈在多個層(例如,UI、業務邏輯、數據訪問),導致理解和維護該功能的困難。

  • 部署一個小的更改通常需要重新部署整個應用程序,從而增加了停機和故障的風險。

  • 如果將所有對數據庫的訪問封裝爲一個 RPC 服務,每次部署都會影響多個業務。

  • 如果每個業務都單獨有一個 RPC,成本會相對較高。

  • 一個業務存在問題,可能也會影響其他業務。

  • 成本相對高,一個業務微服務至少有兩個應用(HTTP、RPC)。

性能

垂直切片:同一個業務:優;跨業務:良(通過 RPC 調用)

  • 所有業務邏輯和數據存取都在同一個服務內部完成,減少了網絡通信開銷和延遲。

水平切片:同一個業務:良;跨業務:良(HTTP 至少有一次 RPC 調用)

  • 不同的業務服務可以之間可以直接通過代碼引用調用,性能更好、開發更方便。但是,也會導致業務依賴混亂。

開發管理

垂直切片:優

  • 不同微服務使用不同的 git 倉庫。

  • 允許團隊獨立處理不同的功能,而無需互相干擾,從而促進更好的協作和並行開發。

  • 不同業務的多個需求並行,git merge 友好,分支管理更容易

水平切片:良

  • 同一個代碼倉庫,所有人都會推代碼,並行開發複雜。

垂直切片似乎看着處處比水平切片好,但是垂直切片的劃分,需要先驗知識,需要有一個能夠熟知業務的工程師能夠先識別業務微服務,然後才能更好的實現代碼重構。

多系統部署

很多時候,我們會有多個系統,例如給普通用戶使用系統的 http api,內部管理用戶用的 admin 系統、給兄弟團隊用的 rpc。在垂直切片的基礎上延伸,我們可以使用下面的架構。

Host

每個 Host 代表一個進程,可以單獨部署爲一個應用程序。比如,http-api 可以給外部用戶使用,admin-api 可以給內部的管理系統使用,internal-rpc 可以給其他團隊獲取數據使用。

Host 僅做以下能力:

共享模塊

ApplicationService、DomainService、Infrastructure 都是單獨的模塊,裏面對應着 DDD 中的各項職責。

Common 也是單獨的模塊,但是 Common 用於存放的是跨業務微服務或者與業務微服務無關的技術組件。

共享模塊設計,類似於 DDD 中的共享內核,適用於多個團隊或系統在某些功能上需要高度協作,且要求業務邏輯一致。

參考

寫的過程中,可能存在一些缺失。

垂直分片

倉儲層

ACL

阿里技術專家詳解 DDD 系列(殷浩)(強烈推薦)

  1. https://mp.weixin.qq.com/s/kpXklmidsidZEiHNw57QAQ

  2. https://mp.weixin.qq.com/s/MU1rqpQ1aA1p7OtXqVVwxQ

  3. https://mp.weixin.qq.com/s/1bcymUcjCkOdvVygunShmw

  4. https://mp.weixin.qq.com/s/w1zqhWGuDPsCayiOgfxk6w

  5. https://mp.weixin.qq.com/s/1rdnkROdcNw5ro4ct99SqQ

CQRS

事件發件箱

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/_SNgL3YH2B3s--bib4l3nw