DDD 必備架構 - 六邊形架構
架構是研究 “分” 和“合”的藝術,通過 “分離關注點” 將系統拆分爲多個部分,然後在 “原則和規則” 的約束下對組件進行裝配,形成高內聚的構件;再根據需求對多個構件進行關聯,形成低耦合的連接,最終構建 “高內聚低耦合” 的軟件系統。
爲了有效應對軟件複雜性,通常會對其進行分類,然後對症下藥逐個擊破。
1. 軟件系統複雜性
面對一個軟件需求,我們經常會將其分爲兩類:
-
功能性需求。就是產品提出的衆多業務功能,例如:用戶登錄、查詢數據、添加訂單等;
-
非功能性需求。指系統在實現功能時必須滿足的技術指標,最常見的包括性能、可靠性、安全性、可維護性、易用性等,例如:系統的響應時間、併發訪問量、容錯能力、數據安全性、可擴展性等。
其實,這兩類需求整好與軟件系統的兩類 “複雜性” 一一對應:
-
業務複雜性,指系統中業務邏輯和業務規則的複雜程度。業務複雜性主要來自於業務的規模、結構、變化性等,這個與軟件所在領域有極大關係;
-
技術複雜性,指系統中所用技術的複雜程度。技術複雜性主要來自於所使用的數據庫、網絡協議、中間件、應用框架等,這與軟件架構和基礎設施關係巨大;
面對不同的需求和複雜性,其設計目標和應對方式也完全不同。
1.1. 業務複雜性
首先,看下業務複雜性:
對於業務需求,我們追求的是系統的複用性和擴展性。
-
複用性。指組件或模塊能夠在不同的場景下被重複使用。高複用性設計能夠大幅度減少開發和維護的成本,提高開發效率。組件的粒度越小複用性越高,功能結構越清晰,邏輯調整越便利,與之相反的是代碼重複率,重複代碼是系統腐化的重要標誌;
-
擴展性。能夠在不改變現有系統結構的情況下,方便地添加新的功能或修改現有功能。具有高擴展性的系統能夠滿足未來需求的變化,降低系統的維護成本。
導致業務代碼變化的原因有很多,比如:
-
線上 bug。線上 bug 很少但對業務系統的傷害巨大,發現 bug 後爲了快速修復問題,往往選擇短平快的方式而非最佳方案,這些 “補丁” 就像系統中的 “飛線” 在代碼中穿梭,成爲超出三界的定時炸彈,一不小心就會給你意外的“驚喜”;
-
新功能需求。這是代碼膨脹的主要推動力,開發人員將產品提出的需求翻譯成代碼,不停的 “塞入” 到代碼倉庫,導致倉庫快速膨脹,很快你將面對幾十萬行代碼並在之上進行新的開發;
-
創新性業務。意味着新建系統、新建倉庫、新建服務,看起來一切非常良好,可以一次性甩掉多年的歷史包袱,但公司整個系統變得越來越複雜,甚至沒人能說出服務間的調用關係;
這些變更都會導致業務代碼越來越多、邏輯越來越複雜,最終變得難以維護。
爲了更好的應對這些變化,常見的手段包括:
-
DDD, 構建於 “領域模型” 基礎之上,使用面向對象的各種語言特性,實現邏輯的封裝、複用;將業務概念和實現組件結合在一起,避免相互轉化,從而降低溝通成本;
-
重構,隨着業務的變化,對原有代碼結構進行優化,在新結構上以擴展的方式完成新功能的添加,不斷地對代碼結構進行調優
-
TDD,重構的重要保障,在優化代碼結構的同時,保障不會破壞原有的業務邏輯
業務複雜性先簡單介紹到這,接下來看下技術複雜性:
1.2. 技術複雜性
對於非功能需求,我們追求的是系統的高性能和高可用。
-
高性能。在同等資源下,要麼讓系統運行儘可能快,要麼讓系統吞吐儘可能大
-
高可用。儘量保障系統 7 * 24h 不間斷的提供服務,避免由於服務中斷導致公司損失
導致技術複雜性激增的原因有很多,比如:
-
用戶和併發量。隨着用戶和併發量的激增,系統承受的壓力將越來越大,一個小小的卡點便能造成巨大的損失
-
系統數據量。隨着系統數據量不斷積累,當單表數據量超過 億 級,不管是訪問速度還是異常恢復都將面臨巨大挑戰
-
機器規模。當機器規模超過一定的閾值,硬件問題將頻繁爆發,基本每天都會出現硬件故障,最終成爲高可用的阻力
這些問題並不能通過簡單的增加代碼來解決,往往需要引入新技術或使用新方案:
-
新技術。當數據庫表數據量達到 “億” 級出現明顯的性能瓶頸時,可以應用 分庫分表 或 TiDB 來解決
-
新方案。當數據庫讀壓力巨大,可以調整技術方案,使用數據庫讀寫分離、增加緩存等方案解決
通過對比,是否有一種感覺:“業務” 與 “技術” 差距巨大,可以說是天壤之別。所以需要一種架構,能夠對 “業務” 和 “技術” 進行隔離,降低兩者的相互影響,使得:
-
技術調整不影響業務模型
-
業務調整不能依賴技術
六邊形架構,便可以解決這個問題。
2. 六邊形架構
六邊形架構出自《實現領域驅動設計》一書,與 “洋蔥架構” 非常相似,都能很好的實現 “業務” 與 “技術” 的分離。
在 “Command 側”(詳見 CQRS 架構) DDD 仍舊是最佳解決方案,所以在此使用 “六邊形架構” 作爲頂級架構。
六邊形架構如下:
該架構由內外兩個六邊形組成,這也是其名稱來源:
-
內六邊形屬於業務域,用於應對業務複雜性。
-
外六邊形屬於技術域,用於應對技術複雜性。
-
其中內六邊形是整個系統的核心,外六邊形依賴於內六邊形,而內六邊形不依賴於外六邊形
-
內外兩個六邊形存在清晰的邊界,兩者相互獨立,互不影響,獨自演進
2.1.【業務】內六邊形
內六邊形聚焦於業務邏輯,使用良好的設計應對業務的複雜性;通過提升系統的複用性和擴展性,來應對未來的變化。
2.1.1. DDD
DDD 是解決複雜業務的一把利器,將業務需求和代碼實現相結合,通過對業務領域的深入理解,構建出可複用、可維護、可擴展的領域模型。
DDD 分爲戰略和戰術兩部分,在此重點闡述戰術部分。戰術模型主要包括:
-
實體(Entity):具有唯一標識的領域對象,具有豐富的屬性和行爲,表示需要持續跟蹤的領域概念,比如 用戶、訂單、地址等;
-
值對象(Value Object):沒有唯一標識的領域對象,具有屬性和行爲,一般用於表示某一個領域概念,比如 金額、郵箱、手機號等;
-
聚合根(Aggregate Root):一組由實體和值對象組成的高內聚對象集合,由一個根實體來管理它們,我們也稱之爲聚合根,比如 訂單 + 訂單項便組成了一個聚合,其中訂單爲聚合根;
-
工廠(Factory):創建領域對象的一種機制,也是設計模式在 DDD 中的落地,它隱藏了對象創建細節,並保障創建對象的有效性,主要解決複雜對象初始化問題;
-
存儲庫(Repository):提供對領域對象的持久化和檢索功能,將領域對象從數據存儲細節中分離出來,完成領域對象和存儲引擎的解耦;
-
領域服務(Service):處理領域對象之間的交互,沒有自己的狀態,封裝了一些業務邏輯,主要用於需要多個領域對象相互協作才能完成的業務場景,比如銀行轉賬;
-
領域事件(Event):指在領域內發生的、有意義的、需要被捕捉的事件,主要完成服務內的流程解耦和服務間的系統解耦;
這些領域對象相互協作,共同承載複雜的業務場景,大幅提升了業務的複用性和擴展性。
2.1.2. TDD
也稱爲測試驅動開發,它的基本思想是在編寫代碼之前先編寫測試,然後根據測試來編寫代碼,最後再運行測試。可以幫助開發人員更快地發現並修復代碼中的 bug,還可以讓開發人員更加關注代碼的設計,使代碼更加健壯、可擴展和易維護。
業務模型與基礎設施的解耦將爲你的 TDD 帶來衆多好處:
-
提高測試的速度:使用 Mock 技術可以避免測試過程中涉及到緩慢的網絡或數據庫操作,從而提高測試速度,加快迭代週期;
-
提高測試可靠性:使用 Mock 數據可以降低測試失敗或不可靠的情況,避免由於數據變更所造成的測試不穩定問題;
-
更好的測試隔離:業務邏輯和基礎設施可以並行開發,避免兩者相互干擾而產生不確定行爲;
-
有利於重構:業務邏輯和外部依賴分離,在重構代碼時,可以聚焦於業務邏輯而不必擔心外部依賴的穩定性和正確性;
2.1.3. 兩頂帽子
兩頂帽子,是落地重構的重要開發模式,是一種工作習慣,或者說是一種高效的工作流程。
兩頂帽子法,將軟件變更落地過程分爲兩個階段:
-
優化結構階段。在不改變軟件行爲的前提下,對軟件結構進行優化,使其更具擴展性。比如:
-
【重構】抽取公共邏輯到方法、類,以便更好的被複用;
-
【設計模式】抽取模板方法,對核心邏輯進行統一;
-
【架構模式】抽取功能微內核,確定插件簽名和功能;
-
添加功能階段。在調整後的具備更好的擴展性的代碼基礎上完成功能調整或者增加新功能,如:
-
新功能複用已有組件能力,避免重複開發;
-
以擴展點的方式增加新功能,提升開發效率;
2.2.【技術】外六邊形
外六邊形聚焦於技術,與公司基礎設施密切相關,也受所用框架的各種約束。其核心是發揮框架和中間件的優勢,更好的爲業務提供服務。
根據應用場景的不同,我們將外六邊形的組件分成兩類:
-
輸入適配器。將來自外部的數據轉換爲系統可以使用的格式;
-
輸出適配器。將系統內部的數據轉換爲外部可以使用的格式
他們是系統與外部的 “翻譯官”,將外部請求轉換爲系統可以理解的指令,並將系統響應轉換爲外部世界可以理解的格式,這種松耦合可以使系統更加靈活更具擴展性。
2.2.1. 輸入適配器
負責將外部系統的請求轉換爲應用服務能夠處理的格式,通常包括 Web 請求、RPC 調用、消息隊列、定時任務等,是將外部請求轉換爲內部指令的橋樑。
常見的輸入適配器主要包括:
-
Web 請求。處理 HTTP 請求,對請求進行轉換、驗證,將其轉換爲應用服務所需格式,調用接口完成業務邏輯,最後將處理結果轉化爲所需格式進行返回。常見的框架有 Spring MVC、Struct2 等;
-
RPC 調用。處理遠程調用請求,將請求轉換爲應用程序所需格式,調用應用服務接口完成業務邏輯,最後返回處理結果;在 Spring Cloud 技術棧下,與 Web 請求高度類似,但仍舊具有自己的特點,需要與 Web 請求進行區分處理。常見的 RPC 框架有 Spring Cloud、gRPC、Thrift、Dubbo 等;
-
消息隊列。主要指的是消息隊列的消費端,從消息隊列中讀取消息,將信息轉換爲應用程序所需格式,調用應用服務接口完成業務邏輯。常見的有 RocketMQ、Kafka、RabbitMQ 等;
-
定時任務。由定時器週期性觸發,調用應用服務的業務方法,完成某種後臺任務。常見的有 Quartz、XXL-job、Spring Task 等;
2.2.2. 輸出適配器
負責將應用程序輸出結果轉換爲外部系統能夠理解的格式,通常包括數據庫、RPC 調用、緩存、搜索、消息隊列、文件系統等,是將內部響應轉換爲外部響應的橋樑。
常見的輸出適配器主要包括:
-
數據庫。將領域模型中的模型數據保存到數據庫進行持久化存儲,常用的框架包括 MyBatis、Jpa、Hibernate 等,中間件主要是 MySQL;
-
緩存。模型數據發生變更後,對緩存數據進行清理或更新,常見框架包括本地緩存 Guava、Caffeine、EhCache,分佈式緩存有 Redis、Memcache、Tair 等;
-
搜索。爲應對多維度查詢,系統會引入搜索引擎組件,在模型數據發生變更後,需要將變更同步到搜索引擎,常見的有 Elasticsearch、Solr、Sphinx 等;
-
消息隊列。這裏主要指的是消息隊列的發送端,當業務操作完成後,系統會向外發佈領域事件,以將變更通知到下游系統,常見的有 RocketMQ、Kafka、RabbitMQ 等;
-
RPC 調用。這裏主要指的是 RPC 的調用端,當業務模型對其他領域服務存在依賴時,需要通過 RPC 進行系統通信,常見的 RPC 框架有 Spring Cloud、gRPC、Thrift、Dubbo 等;
-
文件系統。這裏簡單理解爲系統的日誌輸出即可,常見的有 Log4j、Logback、SLF4J、JUL 等;
這麼多紛繁複雜的框架、中間件從另一個層面也驗證了,外六邊形是以技術作爲驅動的,其核心就是:如何更好的使用這些技術工具,發揮每個框架的強項。
3. 小結
任意一個業務系統都會面對兩類需求:
-
來自業務的功能性需求;
-
來自技術的非功能性需求;
兩類需求背後的驅動力(複雜性)完全不同:
-
功能性需求的驅動力在於業務自身的複雜性和多變性;
-
非功能性需求的驅動力在於架構的變更和技術的更迭;
爲了更好的應對這兩類變化,需要在架構上進行隔離,避免相互影響帶來更多的複雜性,然後逐個擊破。這就是六邊形架構最擅長的領域:
-
內六邊形。聚焦於業務解決功能性需求,常用的手段有 DDD、TDD、重構;
-
外六邊形。聚焦於技術解決非功能性需求,需要使用好 輸入適配器 和 輸出適配器;
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/L2PEKwbvAlA-MFfIJH2zEQ