DDD學習筆記 - 總結篇

19 | 總結(一):微服務設計和拆分要堅持哪些原則

課程鏈接:https://time.geekbang.org/column/article/171185

由於企業發展歷程以及企業技術和文化的不同,DDD 和微服務的實施策略也會有差異。那麼面對這種差異,應該如何落地 DDD 和微服務呢?

 

微服務的演進策略

在從單體向微服務演進時,演進策略大體分爲兩種:絞殺者策略和修繕者策略。

1. 絞殺者策略

絞殺者策略是一種逐步剝離業務能力,用微服務逐步替代原有單體系統的策略。它對單體系統進行領域建模,根據領域邊界,在單體系統之外,將新功能和部分業務能力獨立出來,建設獨立的微服務。新微服務與單體系統保持松耦合關係。

隨着時間的推移,大部分單體系統的功能將被獨立爲微服務,這樣就慢慢絞殺掉了原來的單體系統。絞殺者策略類似建築拆遷,完成部分新建築物後,然後拆除部分舊建築物。

2. 修繕者策略

修繕者策略是一種維持原有系統整體能力不變,逐步優化系統整體能力的策略。它是在現有系統的基礎上,剝離影響整體業務的部分功能,獨立爲微服務,比如高性能要求的功能,代碼質量不高或者版本發佈頻率不一致的功能等。

通過這些功能的剝離,就可以兼顧整體和局部,解決系統整體不協調的問題。修繕者策略類似古建築修復,將存在問題的部分功能重建或者修復後,重新加入到原有的建築中,保持建築原貌和功能不變。一般人從外表感覺不到這個變化,但是建築物質量卻得到了很大的提升。

其實還有第三種策略,就是另起爐竈,顧名思義就是將原有的系統推倒重做。建設期間,原有單體系統照常運行,一般會停止開發新需求。而新系統則會組織新的項目團隊,按照原有系統的功能域,重新做領域建模,開發新的微服務。在完成數據遷移後,進行新舊系統切換。

對於大型核心系統一般不建議採用這種策略,這是因爲系統重構後的不穩定性、大量未知的潛在技術風險和新的開發模式下項目團隊磨合等不確定性因素,會導致項目實施難度大大增加。

 

不同場景下的領域建模策略

由於企業內情況千差萬別,發展歷程也不一樣,有遺留單體系統的微服務改造,也有全新未知領域的業務建模和系統設計,還有遺留系統局部優化的情況。不同場景下,領域建模的策略也會有差異。下面就分幾類場景來看看如何進行領域建模。

1. 新建系統

新建系統又分爲簡單和複雜領域建模兩種場景。

簡單領域建模:簡單的業務領域,一個領域就是一個小的子域。在這個小的問題域內,領域建模過程相對簡單,直接採用事件風暴的方法構建領域模型就可以了。

複雜領域建模:對於複雜的業務領域,領域可能需要多級拆分後才能開始領域建模。領域拆分爲子域,甚至子域還需要進一步拆分。比如:保險它需要拆分爲承保、理賠、收付費和再保等子域,承保子域再拆分爲投保、保單管理等子子域。複雜領域如果不做進一步細分,由於問題域太大,領域建模的工程量會非常浩大。不太容易通過事件風暴,完成一個很大的領域建模,即使勉強完成,效果也不一定好。

對於複雜領域,可以分三步來完成領域建模和微服務設計。

第一步,拆分子域建立領域模型。根據業務領域的特點,參考流程節點邊界或功能聚合模塊等邊界因素。結合領域專家和項目團隊的討論,將領域逐級分解爲大小合適的子域,針對子域採用事件風暴,劃分聚合和限界上下文,初步確定子域內的領域模型。

第二步,領域模型微調。梳理領域內所有子域的領域模型,對各子域領域模型進行微調。微調的過程重點考慮不同領域模型中聚合的重組。同步考慮領域模型和聚合的邊界,服務以及事件之間的依賴關係,確定最終的領域模型。

第三步,微服務的設計和拆分。根據領域模型和微服務拆分原則,完成微服務的拆分和設計。

2. 單體遺留系統

如果面對的是一個單體遺留系統,只需要將部分功能獨立爲微服務,而其餘仍爲單體,整體保持不變,比如將面臨性能瓶頸的模塊拆分爲微服務。只需要將這一特定功能,理解爲一個簡單子領域,參考簡單領域建模的方式就可以了。在微服務設計中,還要考慮新老系統之間服務和業務的兼容,必要時可引入防腐層。

 

DDD 使用的誤區

很多人在接觸微服務後,但凡是系統,一概都想設計成微服務架構。其實有些業務場景,單體架構的開發成本會更低,開發效率更高,採用單體架構也不失爲好的選擇。同樣,雖然 DDD 很好,但有些傳統設計方法在微服務設計時依然有它的用武之地。下面就來聊聊 DDD 使用的幾個誤區。

1. 所有的領域都用 DDD

很多人在學會 DDD 後,可能會將其用在所有業務域,即全部使用 DDD 來設計。DDD 從戰略設計到戰術設計,是一個相對複雜的過程,首先企業內要培養 DDD 的文化,其次對團隊成員的設計和技術能力要求相對比較高。在資源有限的情況下,應聚焦核心域,建議先從富領域模型的核心域開始,而不必一下就在全業務域推開。

2. 全部採用 DDD 戰術設計方法

不同的設計方法有它的適用環境,應選擇它最擅長的場景。DDD 有很多的概念和戰術設計方法,比如聚合根和值對象等。聚合根利用倉儲管理聚合內實體數據之間的一致性,這種方法對於管理新建和修改數據非常有效,比如在修改訂單數據時,它可以保證訂單總金額與所有商品明細金額的一致,但它並不擅長較大數據量的查詢處理,甚至有延遲加載進而影響效率的問題。

而傳統的設計方法,可能一條簡單的 SQL 語句就可以很快地解決問題。而很多貧領域模型的業務,比如數據統計和分析,DDD 很多方法可能都用不上,或用得並不順手,而傳統的方法很容易就解決了。

因此,在遵守領域邊界和微服務分層等大原則下,在進行戰術層面設計時,應該選擇最適合的方法,不只是 DDD 設計方法,當然還應該包括傳統的設計方法。這裏要以快速、高效解決實際問題爲最佳,不要爲做 DDD 而做 DDD。

3. 重戰術設計而輕戰略設計

很多 DDD 初學者,學習 DDD 的主要目的,可能是爲了開發微服務,因此更看重 DDD 的戰術設計實現。殊不知 DDD 是一種從領域建模到微服務落地的全方位的解決方案。

戰略設計時構建的領域模型,是微服務設計和開發的輸入,它確定了微服務的邊界、聚合、代碼對象以及服務等關鍵領域對象。領域模型邊界劃分得清不清晰,領域對象定義得明不明確,會決定微服務的設計和開發質量。沒有領域模型的輸入,基於 DDD 的微服務的設計和開發將無從談起。因此不僅要重視戰術設計,更要重視戰略設計。

4. DDD 只適用於微服務

DDD 是在微服務出現後才真正火爆起來的,很多人會認爲 DDD 只適用於微服務。在 DDD 沉默的二十多年裏,其實它一直也被應用在單體應用的設計中。

具體項目實施時,要吸取 DDD 的核心設計思想和理念,結合具體的業務場景和團隊技術特點,多種方法組合,靈活運用,用正確的方式解決實際問題。

 

微服務設計原則

微服務設計原則中,如高內聚低耦合、複用、單一職責等這些常見的設計原則在此就不贅述了,主要強調下面這幾條:

第一條:要領域驅動設計,而不是數據驅動設計,也不是界面驅動設計。

微服務設計首先應建立領域模型,確定邏輯和物理邊界以及領域對象後,然後纔開始微服務的拆分和設計。而不是先定義數據模型和庫表結構,也不是前端界面需要什麼,就去調整核心領域邏輯代碼。在設計時應該將外部需求從外到內逐級消化,儘量降低對核心領域層邏輯的影響。

第二條:要邊界清晰的微服務,而不是泥球小單體。

微服務上線後其功能和代碼也不是一成不變的。隨着需求或設計變化,領域模型會迭代,微服務的代碼也會分分合合。邊界清晰的微服務,可快速實現微服務代碼的重組。微服務內聚合之間的領域服務和數據庫實體原則上應杜絕相互依賴。可通過應用服務編排或者事件驅動,實現聚合之間的解耦,以便微服務的架構演進。

第三條:要職能清晰的分層,而不是什麼都放的大籮筐。

分層架構中各層職能定位清晰,且都只能與其下方的層發生依賴,也就是說只能從外層調用內層服務,內層通過封裝、組合或編排對外逐層暴露,服務粒度也由細到粗。應用層負責服務的組合和編排,不應有太多的核心業務邏輯,領域層負責核心領域業務邏輯的實現。各層應各司其職,職責邊界不要混亂。在服務演進時,應儘量將可複用的能力向下層沉澱。

第四條:要做自己能 hold 住的微服務,而不是過度拆分的微服務。

微服務過度拆分必然會帶來軟件維護成本的上升,比如:集成成本、運維成本、監控和定位問題的成本。企業在微服務轉型過程中還需要有云計算、DevOps、自動化監控等能力,而一般企業很難在短時間內提升這些能力,如果項目團隊沒有這些能力,將很難 hold 住這些微服務。

如果在微服務設計之初按照 DDD 的戰略設計方法,定義好了微服務內的邏輯邊界,做好了架構的分層,其實不必拆分太多的微服務,即使是單體也未嘗不可。隨着技術積累和能力提升,當有了這些能力後,由於應用內有清晰的邏輯邊界,可以隨時輕鬆地重組出新的微服務,而這個過程不會花費太多的時間和精力。

 

微服務拆分需要考慮哪些因素?

理論上一個限界上下文內的領域模型可以被設計爲微服務,但是由於領域建模主要從業務視角出發,沒有考慮非業務因素,比如需求變更頻率、高性能、安全、團隊以及技術異構等因素,而這些非業務因素對於領域模型的系統落地也會起到決定性作用,因此在微服務拆分時需要重點考慮它們。列出了以下主要因素供參考。

1. 基於領域模型:基於領域模型進行拆分,圍繞業務領域按職責單一性、功能完整性拆分。

2. 基於業務需求變化頻率:識別領域模型中的業務需求變動頻繁的功能,考慮業務變更頻率與相關度,將業務需求變動較高和功能相對穩定的業務進行分離。這是因爲需求的經常性變動必然會導致代碼的頻繁修改和版本發佈,這種分離可以有效降低頻繁變動的敏態業務對穩態業務的影響。

3. 基於應用性能:識別領域模型中性能壓力較大的功能。因爲性能要求高的功能可能會拖累其它功能,在資源要求上也會有區別,爲了避免對整體性能和資源的影響,可以把在性能方面有較高要求的功能拆分出去。

4. 基於組織架構和團隊規:模除非有意識地優化組織架構,否則微服務的拆分應儘量避免帶來團隊和組織架構的調整,避免由於功能的重新劃分,而增加大量且不必要的團隊之間的溝通成本。拆分後的微服務項目團隊規模保持在 10~12 人左右爲宜。

5. 基於安全邊界:有特殊安全要求的功能,應從領域模型中拆分獨立,避免相互影響。

6. 基於技術異構等因素:領域模型中有些功能雖然在同一個業務域內,但在技術實現時可能會存在較大的差異,也就是說領域模型內部不同的功能存在技術異構的問題。由於業務場景或者技術條件的限制,有的可能用.NET,有的則是 Java,有的甚至大數據架構。對於這些存在技術異構的功能,可以考慮按照技術邊界進行拆分。

 

======================================================================================================

 

 

20 | 總結(二):分佈式架構關鍵設計10問

課程鏈接:https://time.geekbang.org/column/article/171185

一、選擇什麼樣的分佈式數據庫?

分佈式架構下的數據應用場景遠比集中式架構複雜,會產生很多數據相關的問題。談到數據,首先就是要選擇合適的分佈式數據庫。

分佈式數據庫大多采用數據多副本的方式,實現數據訪問的高性能、多活和容災。目前主要有三種不同的分佈式數據庫解決方案。它們的主要差異是數據多副本的處理方式和數據庫中間件。

1. 一體化分佈式數據庫方案

它支持數據多副本、高可用。多采用 Paxos 協議,一次寫入多數據副本,多數副本寫入成功即算成功。代表產品是 OceanBase 和高斯數據庫。

2. 集中式數據庫 + 數據庫中間件方案

它是集中式數據庫與數據庫中間件結合的方案,通過數據庫中間件實現數據路由和全局數據管理。數據庫中間件和數據庫獨立部署,採用數據庫自身的同步機制實現主副本數據的一致性。集中式數據庫主要有 MySQL 和 PostgreSQL 數據庫,基於這兩種數據庫衍生出了很多的解決方案,比如開源數據庫中間件 MyCat+MySQL 方案,TBase(基於 PostgreSQL,但做了比較大的封裝和改動)等方案。

3. 集中式數據庫 + 分庫類庫方案

它是一種輕量級的數據庫中間件方案,分庫類庫實際上是一個基礎 JAR 包,與應用軟件部署在一起,實現數據路由和數據歸集。它適合比較簡單的讀寫交易場景,在強一致性和聚合分析查詢方面相對較弱。典型分庫基礎組件有 ShardingSphere。

小結:這三種方案實施成本不一樣,業務支持能力差異也比較大。一體化分佈式數據庫主要由互聯網大廠開發,具有超強的數據處理能力,大多需要雲計算底座,實施成本和技術能力要求比較高。集中式數據庫 + 數據庫中間件方案,實施成本和技術能力要求適中,可滿足中大型企業業務要求。第三種分庫類庫的方案可處理簡單的業務場景,成本和技能要求相對較低。在選擇數據庫的時候,要考慮自身能力、成本以及業務需要,從而選擇合適的方案。

 

二、如何設計數據庫分庫主鍵?

選擇了分佈式數據庫,第二步就要考慮數據分庫,這時分庫主鍵的設計就很關鍵了。

與客戶接觸的關鍵業務,建議以客戶 ID 作爲分庫主鍵。這樣可以確保同一個客戶的數據分佈在同一個數據單元內,避免出現跨數據單元的頻繁數據訪問。跨數據中心的頻繁服務調用或跨數據單元的查詢,會對系統性能造成致命的影響。

將客戶的所有數據放在同一個數據單元,對客戶來說也更容易提供客戶一致性服務。而對企業來說,“以客戶爲中心”的業務能力,首先就要做到數據上的“以客戶爲中心”。

當然,也可以根據業務需要用其它的業務屬性作爲分庫主鍵,比如機構、用戶等。

 

三、數據庫的數據同步和複製

在微服務架構中,數據被進一步分割。爲了實現數據的整合,數據庫之間批量數據同步與複製是必不可少的。數據同步與複製主要用於數據庫之間的數據同步,實現業務數據遷移、數據備份、不同渠道核心業務數據向數據平臺或數據中臺的數據複製、以及不同主題數據的整合等。

傳統的數據傳輸方式有 ETL 工具和定時提數程序,但數據在時效性方面存在短板。分佈式架構一般採用基於數據庫邏輯日誌增量數據捕獲(CDC)技術,它可以實現準實時的數據複製和傳輸,實現數據處理與應用邏輯解耦,使用起來更加簡單便捷。

現在主流的 PostgreSQL 和 MySQL 數據庫外圍,有很多數據庫日誌捕獲技術組件。CDC 也可以用在領域事件驅動設計中,作爲領域事件增量數據的獲取技術。

 

四、跨庫關聯查詢如何處理?

跨庫關聯查詢是分佈式數據庫的一個短板,會影響查詢性能。在領域建模時,很多實體會分散到不同的微服務中,但很多時候會因爲業務需求,它們之間需要關聯查詢。

關聯查詢的業務場景包括兩類:第一類是基於某一維度或某一主題域的數據查詢,比如基於客戶全業務視圖的數據查詢,這種查詢會跨多個業務線的微服務;第二類是表與表之間的關聯查詢,比如機構表與業務表的聯表查詢,但機構表和業務表分散在不同的微服務。

如何解決這兩類關聯查詢呢?對於第一類場景,由於數據分散在不同微服務裏,無法跨多個微服務來統計這些數據。可以建立面向主題的分佈式數據庫,它的數據來源於不同業務的微服務。採用數據庫日誌捕獲技術,從各業務端微服務將數據準實時彙集到主題數據庫。在數據彙集時,提前做好數據關聯(如將多表數據合併爲一個寬表)或者建立數據模型。面向主題數據庫建設查詢微服務。這樣一次查詢就可以獲取客戶所有維度的業務數據了。還可以根據主題或場景設計合適的分庫主鍵,提高查詢效率。

對於第二類場景,對於不在同一個數據庫的表與表之間的關聯查詢場景,可以採用小表廣播,在業務庫中增加一張冗餘的代碼副表。當主表數據發生變化時,可以通過消息發佈和訂閱的領域事件驅動模式,異步刷新所有副表數據。這樣既可以解決表與表的關聯查詢,還可以提高數據的查詢效率。

 

五、如何處理高頻熱點數據?

對於高頻熱點數據,比如商品、機構等代碼類數據,它們同時面向多個應用,要有很高的併發響應能力。它們會給數據庫帶來巨大的訪問壓力,影響系統的性能。

常見的做法是將這些高頻熱點數據,從數據庫加載到如 Redis 等緩存中,通過緩存提供數據訪問服務。這樣既可以降低數據庫的壓力,還可以提高數據的訪問性能。

另外,對需要模糊查詢的高頻數據,也可以選用 ElasticSearch 等搜索引擎。

緩存就像調味料一樣,投入小、見效快,用戶體驗提升快。

 

六、前後序業務數據的處理

在微服務設計時會經常發現,某些數據需要關聯前序微服務的數據。比如:在保險業務中,投保微服務生成投保單後,保單會關聯前序投保單數據等。在電商業務中,貨物運輸單會關聯前序訂單數據。由於關聯的數據分散在業務的前序微服務中,無法通過不同微服務的數據庫來給它們建立數據關聯。

如何解決這種前後序的實體關聯呢?

一般來說,前後序的數據都跟領域事件有關。可以通過領域事件處理機制,按需將前序數據通過領域事件實體,傳輸並冗餘到當前的微服務數據庫中。

可以將前序數據設計爲實體或者值對象,並被當前實體引用。在設計時需要關注以下內容:如果前序數據在當前微服務只可整體修改,並且不會對它做查詢和統計分析,可以將它設計爲值對象;當前序數據是多條,並且需要做查詢和統計分析,可以將它設計爲實體。

這樣,可以在貨物運輸微服務,一次獲取前序訂單的清單數據和貨物運輸單數據,將所有數據一次反饋給前端應用,降低跨微服務的調用。如果前序數據被設計爲實體,還可以將前序數據作爲查詢條件,在本地微服務完成多維度的綜合數據查詢。只有必要時才從前序微服務,獲取前序實體的明細數據。這樣,既可以保證數據的完整性,還可以降低微服務的依賴,減少跨微服務調用,提升系統性能。

 

七、數據中臺與企業級數據集成

分佈式微服務架構雖然提升了應用彈性和高可用能力,但原來集中的數據會隨着微服務拆分而形成很多數據孤島,增加數據集成和企業級數據使用的難度。可以通過數據中臺來實現數據融合,解決分佈式架構下的數據應用和集成問題。

可以分三步來建設數據中臺。

第一,按照統一數據標準,完成不同微服務和渠道業務數據的彙集和存儲,解決數據孤島和初級數據共享的問題。

第二,建立主題數據模型,按照不同主題和場景對數據進行加工處理,建立面向不同主題的數據視圖,比如客戶統一視圖、代理人視圖和渠道視圖等。

第三,建立業務需求驅動的數據體系,支持業務和商業模式創新。數據中臺不僅限於分析場景,也適用於交易型場景。可以建立在數據倉庫和數據平臺上,將數據平臺化之後提供給前臺業務使用,爲交易場景提供支持。

 

八、BFF 與企業級業務編排和協同

企業級業務流程往往是多個微服務一起協作完成的,每個單一職責的微服務就像積木塊,它們只完成自己特定的功能。那如何組織這些微服務,完成企業級業務編排和協同呢?

可以在微服務和前端應用之間,增加一層 BFF 微服務(Backend for Frontends)。BFF 主要職責是處理微服務之間的服務組合和編排,微服務內的應用服務也是處理服務的組合和編排,那這二者有什麼差異呢?

BFF 位於中臺微服務之上,主要職責是微服務之間的服務協調;應用服務主要處理微服務內的服務組合和編排。在設計時應儘可能地將可複用的服務能力往下層沉澱,在實現能力複用的同時,還可以避免跨中心的服務調用。

BFF 像齒輪一樣,來適配前端應用與微服務之間的步調。它通過 Façade 服務適配不同的前端,通過服務組合和編排,組織和協調微服務。BFF 微服務可根據需求和流程變化,與前端應用版本協同發佈,避免中臺微服務爲適配前端需求的變化,而頻繁地修改和發佈版本,從而保證微服務核心領域邏輯的穩定。

如果 BFF 做得足夠強大,它就是一個集成了不同中臺微服務能力、面向多渠道應用的業務能力平臺。

 

九、分佈式事務還是事件驅動機制?

分佈式架構下,原來單體的內部調用,會變成分佈式調用。如果一個操作涉及多個微服務的數據修改,就會產生數據一致性的問題。數據一致性有強一致性和最終一致性兩種,它們實現方案不一樣,實施代價也不一樣。

對於實時性要求高的強一致性業務場景,可以採用分佈式事務,但分佈式事務有性能代價,在設計時需平衡考慮業務拆分、數據一致性、性能和實現的複雜度,儘量避免分佈式事務的產生。

領域事件驅動的異步方式是分佈式架構常用的設計方法,它可以解決非實時場景的數據最終一致性問題。基於消息中間件的領域事件發佈和訂閱,可以很好地解耦微服務。通過削峯填谷,可以減輕數據庫實時訪問壓力,提高業務吞吐量和處理能力。還可以通過事件驅動實現讀寫分離,提高數據庫訪問性能。對最終一致性的場景,建議採用領域事件驅動的設計方法。

 

十、多中心多活的設計

分佈式架構的高可用主要通過多活設計來實現,多中心多活是一個非常複雜的工程,下面主要列出以下幾個關鍵的設計。

1. 選擇合適的分佈式數據庫。數據庫應該支持多數據中心部署,滿足數據多副本以及數據底層複製和同步技術要求,以及數據恢復的時效性要求。

2. 單元化架構設計。將若干個應用組成的業務單元作爲部署的基本單位,實現同城和異地多活部署,以及跨中心彈性擴容。各單元業務功能自包含,所有業務流程都可在本單元完成;任意單元的數據在多個數據中心有副本,不會因故障而造成數據丟失;任何單元故障不影響其它同類單元的正常運行。單元化設計時要儘量避免跨數據中心和單元的調用。

3. 訪問路由。訪問路由包括接入層、應用層和數據層的路由,確保前端訪問能夠按照路由準確到達數據中心和業務單元,準確寫入或獲取業務數據所在的數據庫。

4. 全局配置數據管理。實現各數據中心全局配置數據的統一管理,每個數據中心全局配置數據實時同步,保證數據的一致性。

 

 

 

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章