1. 程式人生 > >理論篇:當中臺遇上DDD,我們該如何設計微服務

理論篇:當中臺遇上DDD,我們該如何設計微服務

借用當下最流行的段子做個開場白。

“設計原則千萬條,高內聚低耦合第一條,架構設計不規範,開發運維兩行淚!”。

在分散式架構下,單體應用被拆分為多個微服務,為了保證微服務的單一職責和合理拆分,“高內聚、鬆耦合”是最寶貴的設計原則。

通俗點講,高內聚就是把相關的行為聚集在一起,把不相關的行為放在別處,如果你要修改某個服務的行為,最好只在一處修改。如果做到了服務之間的鬆耦合,那麼修改一個服務就不需要修改另一服務,一個鬆耦合的服務應該儘可能少的知道與之協作的那些服務的資訊。

從集中式架構向分散式架構的技術轉型,正如從蓋磚瓦房向蓋高樓大廈轉變一樣,必然要有組織、文化、理念和設計方法的同步更新,其中最不可或缺的能力就是架構設計能力。

如何做到“高內聚、低耦合”?我們先來學習幾種典型的微服務架構模型。

微服務架構模型

整潔架構(又名洋蔥架構)

在整潔架構裡,同心圓代表應用軟體的不同部分,從裡到外依次是領域模型、領域服務、應用服務、最外圍是容易變化的內容,如介面和基礎設施(如資料儲存等)。整潔架構是以領域模型為中心,不是以資料為中心。

\"1\"

整潔架構

整潔架構最主要原則是依賴原則,它定義了各層的依賴關係,越往裡,依賴越低,程式碼級別越高。外圓程式碼依賴只能指向內圓,內圓不知道外圓的任何事情。一般來說,外圓的宣告(包括方法、類、變數)不能被內圓引用。同樣的,外圓使用的資料格式也不能被內圓使用。

整潔架構各層主要職能如下:

Entities:實現領域核心心業務邏輯,它封裝了企業級的業務規則。一個Entity可以是一個帶方法的物件,也可以是一個數據結構和方法集合。

Use Cases:實現與使用者操作相關的服務組合與編排,它包含了應用特有的業務規則,封裝和實現了系統的所有用例。

Interface Adapters:它把適用於Use Cases和entities的資料轉換為適用於外部服務的格式,或把外部的資料格式轉換為適用於Use Casess和entities的格式。

Frameworks and Drivers:這是實現所有前端業務細節的地方:UI,Tools,Frameworks等。

六邊形架構(又名埠介面卡架構)

追溯微服務架構的淵源,一般會涉及到六邊形架構。六邊形架構的核心理念是:應用是通過埠與外部進行互動的,這也是微服務架構下API閘道器盛行的主要原因。六邊形架構中,內部業務邏輯(應用層和領域模型)與外部資源(APP,WEB應用以及資料庫資源等)完全隔離,僅通過介面卡進行互動。它解決了業務邏輯與使用者介面的程式碼交錯的主要問題,從而可以很好的實現前後端分離。

\"2\"

六邊形架構

六邊形架構將系統分為內部和外部兩層六邊形,內部六邊形代表了應用的核心業務邏輯,外部六邊形代表外部應用、驅動和基礎資源等。內部通過埠和介面卡與外部通訊,對應用以API主動適配的方式提供服務,對資源通過依賴反轉被動適配資源的形式呈現。一個埠可能對應多個外部系統,不同的外部系統使用不同的介面卡,介面卡負責對協議進行轉換。這就使得應用程式能夠以一致的方式被使用者、程式、自動化測試、批處理指令碼所驅動。

六邊形架構各層的依賴關係與整潔架構類似。

CQRS(命令與查詢職責分離)

CQRS就是讀寫分離,讀寫分離的主要目的是為了提高查詢效能,同時達到讀、寫解耦。而DDD和CQRS結合,可以分別對讀和寫建模。

\"3\"

CQRS(命令與查詢職責分離)

查詢模型是一種非規範化資料模型,它不反映領域行為,只用於資料查詢和顯示;命令模型執行領域行為,在領域行為執行完成後通知查詢模型。

命令模型如何通知到查詢模型呢?如果查詢模型和領域模型共享資料來源,則可以省卻這一步;如果沒有共享資料來源,可以藉助於釋出訂閱的訊息模式通知到查詢模型,從而達到資料最終一致性。

Martin在blog中指出:CQRS適用於極少數複雜的業務領域,如果不是很適合反而會增加複雜度;另一個適用場景是為了獲取高效能的查詢服務。

對於寫少讀多的共享類通用資料服務(如主資料類應用)可以採用讀寫分離架構模式。單資料中心寫入資料,通過釋出訂閱模式將資料副本分發到多資料中心。通過查詢模型微服務,實現多資料中心資料共享和查詢。

領域驅動設計分層架構

分層架構的一個重要原則是每層只能與位於其下方的層發生依賴。

分層架構的好處是顯而易見的。

首先,由於層間鬆散的耦合關係,使得我們可以專注於本層的設計,而不必關心其他層的設計,也不必擔心自己的設計會影響其它層,對提高軟體質量大有裨益。其次,分層架構使得程式結構清晰,升級和維護都變得十分容易,更改某層的程式碼,只要本層的介面保持穩定,其他層可以不必修改。即使本層的介面發生變化,也隻影響相鄰的上層,修改工作量小且錯誤可以控制,不會帶來意外的風險。
 
關於分層架構的權威觀點,Martin Fowler在《Patterns of Enterprise Application Architecture》一書中給出了答案: 1. 開發人員只關注整個架構中的某一層。 2. 很容易的用新的方法來替換原有層次的方法。 3. 降低層與層之間的依賴。 4. 有利於標準化。 5. 利於各層邏輯的複用。

要保持程式分層架構的優點,就必須堅持層間的鬆耦合關係。設計程式時,應先劃分出可能的層次,以及此層次提供的介面和需要的介面。設計某層時,應儘量保持層間的隔離,僅使用下層提供的介面。

\"4\"

DDD(領域驅動設計)分層架構

DDD分層架構各層定義與職能:

展現層:它負責向用戶顯示資訊和解釋使用者命令,完成前端介面邏輯。這裡的使用者不一定是使用使用者介面的人,也可以是另一個計算機系統。

應用層:它是很薄的一層,負責展現層與領域層之間的協調,也是與其它系統應用層進行互動的必要渠道。應用層要儘量簡單,不包含業務規則或者知識,不保留業務物件的狀態,只保留有應用任務的進度狀態,更注重流程性的東西。它只為領域層中的領域物件協調任務,分配工作,使它們互相協作。

領域層:它是業務軟體的核心所在,包含了業務所涉及的領域物件(實體、值物件)、領域服務以及它們之間的關係,負責表達業務概念、業務狀態資訊以及業務規則,具體表現形式就是領域模型。領域驅動設計提倡富領域模型,即儘量將業務邏輯歸屬到領域物件上,實在無法歸屬的部分則以領域服務的形式進行定義。

基礎設施層:它向其他層提供通用的技術能力,為應用層傳遞訊息(API閘道器等),為領域層提供持久化機制(如資料庫資源)等。

架構模型對比和分析

雖然整潔架構、六邊形架構以及DDD分層架構三種架構模型展現方式以及解決問題的出發點不一樣,但其架構思想與微服務架構高內聚低耦合的設計原則高度一致。

\"5\"

整潔、六邊形以及DDD三種架構模型關係

突破現象看本質,在變與不變中尋找平衡!

從上圖可以看出,在六邊形架構、DDD分層架構的白框部分以及整潔架構Use Cases和Entities區域實現了核心業務邏輯。但是核心業務邏輯又由兩部分來完成:應用層和領域層邏輯。領域層實現了最核心的業務領域部分的邏輯,對外提供領域模型內細粒度的領域服務,應用層依賴領域層業務邏輯,通過服務組合和編排通過API閘道器向前臺應用提供粗粒度的服務。

系統需求變幻無窮,但變化總是有矩可循的,使用者體驗、操作習慣、市場環境以及管理流程的變化,往往會導致介面邏輯和流程的多變,但總體來說,不管前臺如何變化,核心領域邏輯基本不會大變。把握好這個規律,我們就知道如何設計應用層和領域層,如何進行邏輯劃界了。
 
上述三種架構模型正是通過分層方式來控制需求變化對系統的影響,確保從外向裡受需求影響逐步減小。面向使用者的展現層可以快速響應外部需求進行調整和釋出,靈活多變,應用層通過服務組合和編排實現業務流程的快速適配上線,領域層基本就不需要太多的變化了。這樣設計的好處是可以保證領域層的核心業務邏輯不會因為外部需求和流程的變動而調整,對於建立前臺靈活、中臺穩固的架構能力是很有好處的。

從幾種架構模型看如何進行中臺及微服務設計?

中臺和微服務設計的關鍵在於合理的分層和領域模型的設計!

聚焦領域模型

中臺屬於後端業務領域邏輯範疇,重點關注領域內業務邏輯的實現,通過實現公共需求為前臺應用提供共享服務能力。按DDD的方法,在領域模型建立的過程中會對業務和應用進行清晰的邏輯和物理邊界劃分。領域模型的設計結果會影響到後續的系統模型、架構模型和領域層程式碼模型的設計,最終影響到微服務的拆分和專案落地實施。

合理的架構分層

不要把與領域無關的業務邏輯放在領域層,避免領域業務邏輯被汙染,保證領域層的純潔,只有這樣才能降低領域邏輯受外部變化的影響。在領域和架構模型建立後,程式碼模型的邏輯分層和微服務拆分要具體情況具體分析,根據自身研發和運維能力綜合考慮。

(1) 專案級單應用

對於單應用系統的分層,遵循上述分層架構模型即可,核心領域邏輯在領域層實現,服務的組合和編排在應用層實現,兩者組合形成中臺,通過API對前臺應用提供服務。

從部署和微服務拆分來講,領域層程式碼部署時可能是一個微服務,也可能會根據限界上下文被拆分為多個微服務部署。應用層程式碼如果邏輯複雜,含較多個性業務邏輯,可以根據需要獨立為微服務部署。如果邏輯簡單,且領域層是一個微服務,在劃分好應用層和領域層程式碼邏輯邊界的情況下,如果符合微服務拆分原則,也可以考慮將應用層與領域層程式碼合併為一個微服務部署。

(2)企業級多中臺應用

對於企業級多中臺應用,多箇中臺應用通過API閘道器對外發布API服務。核心域業務中臺在呼叫支撐域和通用域中臺服務時通過核心域應用層完成多中臺服務的組合和編排,為前臺應用提供API服務。核心域中臺的應用層是否獨立成微服務部署,需考慮的情況與單應用系統相似。

服務的管理

應用層、領域層和基礎設施層都有對應的服務,各司其職提供服務,其中基礎設施層的服務通過依賴反轉模式為領域層和應用層提供基礎設施資源服務。應用層和領域層服務釋出在API閘道器,通過API閘道器適配,為前臺提供使用者無差異化(應用app、批處理或自動化測試)的服務。

資源的適配和解耦

由於上述架構模型中定義的外層只能依賴內層的架構原則,對於像資料庫、快取、檔案系統等的外部基礎設施資源,往往採用依賴反轉的模式對外提供資源服務,實現應用層、領域層與基礎設施層資源的解耦。在設計中應考慮資源層的程式碼適配邏輯,一旦基礎設施資源出現變更(如換資料庫),可以遮蔽資源變更對業務程式碼帶來的影響,切斷業務邏輯對基礎資源的依賴,降低由於資源變更對業務邏輯的影響。

前臺應用

從核心業務邏輯來看,中臺實現了主要的業務邏輯,屬於標準化的重量級應用。前臺應用聚焦於介面互動以及業務流程等,屬於輕量級應用,前臺應用可以有個性的業務邏輯、流程和配置資料,甚至資料庫,通過呼叫中臺API服務完成互動介面和業務全流程。

中臺、領域驅動設計及微服務

分析和設計模式的演進

在單機和集中式架構時代,系統分析和設計往往都是分階段割裂進行的,容易導致需求、設計與程式碼實現的不一致,軟體上線後才發現很多功能不是自己想要的,而且在這種模式下,軟體也不能快速響應需求和業務變化。
 
領域驅動設計(DDD)打破了這種隔閡,它提出了領域模型概念,統一了分析、設計和開發語言和過程,使得軟體能夠更靈活快速響應需求變化。

軟體分析和設計方法經歷了三個階段的演進:

第一階段是單機架構時代:採用面向過程的設計方法,系統包括UI層和資料庫兩層,採用C/S架構模式,整個系統圍繞資料庫驅動設計和開發,新專案總是從設計資料庫及其欄位開始。

第二階段是集中式架構時代:採用面向物件的設計方法,系統包括UI層、業務邏輯層和資料庫層,採用經典的三層架構,也有部分應用採用傳統的SOA架構,這種架構易使服務變得臃腫,難於維護拓展,伸縮效能差。這個階段系統分析、軟體設計和開發大多是分階段進行的。

第三階段是分散式架構時代:由於微服務架構的流行,採用領域驅動設計方法,應用系統包括UI層、應用層、領域層和基礎層。這個階段融合了分析和設計階段,通過建立領域模型,劃分領域邊界,做到領域模型既設計,程式碼與設計保持一致。

領域驅動設計主要優勢:1.業務導向。2.業務邏輯內聚,應用邊界清晰。3.建立領域模型優先。4.分析、設計、程式碼和資料有機結合。5.程式碼即設計。6.擴充套件性好。

資料驅動設計主要特點:1.技術導向。2.資料庫優先。3.程式碼不能反映業務和設計。4.業務邏輯分散。5.擴充套件性不好。

領域驅動設計概述

2004年Eric Evans 發表《Domain-Driven Design –Tackling Complexity in the Heart of Software》 (領域驅動設計 )簡稱Evans DDD。但在軟體開發領域一直都是雷聲大,雨點小,領域驅動設計核心思想是通過領域驅動設計方法定義領域模型,從而確定業務和應用邊界,保證業務模型與程式碼模型的一致性。這幾年之所以開始火起來,主要功勞要歸功於隊友“微服務”,領域驅動設計與微服務架構天生匹配。

領域驅動設計(DDD)是一種處理高度複雜域的設計思想,試圖分離技術實現的複雜性,圍繞業務概念構建領域模型來控制業務的複雜性,以解決軟體難以理解,難以演化等問題。團隊利用它可以成功的開發複雜業務軟體系統,在系統變大時仍能保持敏捷性。

領域驅動設計分為兩個階段:

1.以一種領域專家、設計人員、開發人員都能理解的通用語言作為相互交流的工具,在交流的過程中發現領域概念,然後將這些概念設計成一個領域模型;

2.由領域模型驅動軟體設計,用程式碼來實現該領域模型。

領域驅動設計的核心訴求是讓業務架構和系統架構形成繫結關係,當我們去響應業務變化調整業務架構時,系統架構的改變也會隨之發生。在領域驅動設計中業務架構的梳理和系統架構的梳理是同步進行的,其結果是設計出的業務上下文和系統模組結構是繫結的。同時技術架構也是解耦的,可以根據劃分出來的業務上下文的系統架構選擇最合適的實現技術。

領域驅動設計包括戰略設計和戰術設計兩個部分。戰略設計主要關注按領域定義,在限界上下文內形成統一語言,提升業務和技術的溝通效率; 戰術設計主要關注領域設計在落地時與設計模型及實現模型的差異性,減小業務和技術之間的鴻溝。(本文對DDD知識點不做詳述,如需瞭解或學習,請查閱《領域驅動設計:軟體核心複雜性應對之道》和《實現領域驅動》)。

領域驅動設計可能會給你帶來以下收穫:

1、領域驅動設計是一套完整而系統的設計方法,它能帶給你從戰略設計到戰術設計的規範過程,使得你的設計思路能夠更加清晰,設計過程更加規範。

2、領域驅動設計尤其善於處理與領域相關的高複雜度業務的產品研發,通過它可以為你的產品建立一個核心而穩定的領域模型核心,有利於領域知識的傳遞與傳承。

3、領域驅動設計強調團隊與領域專家的合作,能夠幫助團隊建立一個溝通良好的團隊組織,構建一致的架構體系。 領域驅動設計強調對架構與模型的精心打磨,尤其善於處理系統架構的演進設計。

4、領域驅動設計的思想、原則與模式有助於提高團隊成員的架構設計能力。

5、領域驅動設計與微服務架構天生匹配,無論是在新專案中設計微服務架構,還是將系統從單體架構演進到微服務設計,都可以遵循領域驅動設計的架構原則。

為什麼領域驅動設計是微服務架構的最佳設計方法?

領域驅動設計作為一種架構設計方法,微服務作為一種架構風格,兩者從本質上都是為追求高響應力目標而從業務視角去分離複雜度的手段。 兩者都強調從業務出發,其核心要義強調根據業務發展,合理劃分領域邊界,持續調整現有架構,優化現有程式碼,以保持架構和程式碼的生命力(演進式架構) 。
 
領域驅動設計主要關注:業務領域,劃分領域邊界;構建通用語言,高效溝通;對業務進行抽象,建立領域模型;維持業務和程式碼的邏輯一致性。

微服務主要關注:執行時程序間通訊,能夠容錯和故障隔離;去中心化管理資料和去中心化治理;服務可以獨立的開發、測試、構建和部署,按業務組織全功能團隊;高內聚低耦合,職責單一。

如果你的業務焦點在領域和領域邏輯,那麼你就可以選擇DDD進行微服務架構設計。

中臺、DDD與微服務

中臺的定義來源於阿里的中臺戰略(詳見《企業IT架構轉型之道:阿里巴巴中臺戰略思想與架構實戰》鍾華編著)。2015年年底,阿里巴巴集團對外宣佈全面啟動阿里巴巴集團2018年中臺戰略,構建符合數字時代的更具創新性、靈活性的“大中臺、小前臺”組織機制和業務機制,即作為前臺的一線業務會更敏捷、更快速適應瞬息萬變的市場,而中臺將集合整個集團的運營資料能力、產品技術能力,對各前臺業務形成強力支撐。

中臺的本質是提煉各個業務條線的共同需求,並將這些功能打造成元件化產品,然後以API介面的形式提供給前臺各業務部門使用。前臺要做什麼業務,需要什麼資源可以直接找中臺,不需要每次去改動自己的底層,而是在底層不變動的情況下,在更豐富靈活的“大中臺”基礎上獲取支援,讓“小前臺”更加靈活敏捷。

中臺戰略的主要目標是實現公共需求和功能的中臺化共享,減少重複建設和投入,為前臺提供統一的一致服務。至於前臺應用是否可以有資料庫?抑或採用什麼樣的開發技術,這些都不是重點,重點需要考慮的是那些公共需求和需要共享的功能是否通過中臺的方式被前臺使用了。

領域驅動設計中領域的定義:一個領域本質上可以理解為就是一個問題域,只要是同一個領域,那問題域就相同。所以只要我們確定了系統所屬的領域,那這個系統的核心業務,即要解決的關鍵問題、問題的範圍邊界就基本確定了。領域的本質是問題域,問題域可能根據需要逐層細分,因此領域可分解為子域,子域或可繼續分為子子域。。。

在領域驅動設計中根據重要性與功能屬性將領域分為三類子域,分別是:核心子域、支撐子域和通用子域。決定產品和企業獨特競爭力的子域是核心子域,它是業務成功的主要因素和企業的核心競爭力。沒有個性化的訴求,屬於通用功能的子域是通用子域,如登陸認證。 還有一種所提供的功能是必須的,但不是通用也不是企業核心競爭力的子域是支撐子域,如單證。

\"6\"

DDD:核心域、支撐域和通用域

中臺、領域以及微服務屬於不同層面的內容,稍作分解我們理清他們之間的關係。

以保險領域為例,業務中臺大致可分為兩類:第一類是提供保險核心業務服務的專屬業務中臺(如承保、理賠等業務);第二類是支撐核心業務流程完成保險全流程的通用中臺(如主資料、客戶、使用者以及電子保單等)。

專屬業務中臺是保險企業的核心競爭力,對應DDD的核心子域。通用中臺對應DDD支撐子域和通用子域。不同領域可根據領域大小進一步細分多個子域,多個子域可對應到一個業務中臺,一個業務中臺也可能會分解成多個子域。

\"7\"

中臺、領域以及微服務

微服務是技術實現和部署的範疇,實現領域或中臺的業務邏輯,為前臺應用提供服務。領域根據限界上下文可以設計為多個微服務,而如果限界上下文過大,一個微服務也可能會包含多個子領域。

中臺是由多個業務條線的共同需求所構成,是需要共享的業務功能和服務單元的集合,一箇中臺可由一個微服務來實現,也可根據領域驅動設計和微服務拆分原則細分為多個微服務,多個微服務功能集合共同組成一箇中臺。

基於DDD的微服務設計方法

DDD設計包括戰略設計和戰術設計兩個部分。在戰略設計階段主要完成領域建模和服務地圖。在戰術設計階段,通過聚合、實體、值物件以及不同層級的服務,完成微服務的建設和實施。通過DDD可以保證業務模型、系統模型、架構模型以及程式碼模型的一致。

本部分主要討論領域設計方法,如對戰術設計和開發方法感興趣可查閱DDD戰術設計相關資料。

DDD領域設計過程包括產品願景、場景分析、領域建模和服務地圖階段,也可根據需要裁剪不必要的階段和參與角色。領域驅動設計一般經歷2-6周的時間,領域模型設計完成後,即可投入微服務實施。

1、產品願景

產品願景是對產品的頂層價值設計,對產品目標使用者、核心價值、差異化競爭點等策略層資訊達成一致,避免產品在演進過程中偏離方向。

階段輸入:產品初衷、使用者研究、競品知識和差異性想法 。

參與角⾊:業務需求方、產品經理、開發組長和產品發起人。

階段產出:電梯演講畫布。

2、場景分析

場景分析是針對核心使用者及頂層服務的一種定性分析,從⽤戶視角出發,探索問題域中的典型場景分析。同時也是從使用者視角對問題域的探索,產出問題域中需要支撐的場景分類及典型場景,用以支撐領域建模階段。

階段輸⼊:核⼼干係人和服務價值定位。

參與角色:產品經理、開發組長和測試組長。

階段產出:場景分類清單。

3、領域建模

領域建模是通過對業務和問題域進⾏分析,建⽴領域模型,向上通過限界上下⽂指導微服務的邊界設計,向下通過聚合指導實體的物件設計。領域建模主要採用事件風暴方法。

階段輸入:業務領域知識和場景分類清單。

參與角色:領域專家、架構師、產品經理、開發組長和測試組長。

階段產出:聚合模型和限界上下⽂地圖。

4、服務地圖

服務地圖是整個產品服務架構的體現。結合業務與技術因素,對服務的粒度、邊界劃分、集 成關係進⾏梳理,得到反映系統微服務層面設計的服務地圖。

階段輸⼊:限界上下⽂地圖。

參與角⾊:產品經理、開發組長、測試組長和產品發起人。

階段產出:服務地圖。

在進行服務地圖設計時需要考慮以下要素:1. 圍繞限界上下⽂邊界。2. 考慮不同業務變化速度/相關度、釋出頻率。3. 考慮系統非功能性需求,如系統彈性伸縮要求、安全性要求和可⽤性要求。4. 考慮團隊組織和溝通效率。5. 軟體包限制。6.技術和架構的異構。

通過DDD戰略和戰術全流程設計可建立業務架構與系統架構的一一對映,保證業務和程式碼模型的一致性。

\"7\"

DDD的業務架構與系統架構對映建立過程

DDD分層架構中的服務

前面我們談到了DDD的分層架構,分層架構主要包括:展現層、應用層、領域層和基礎層(參考圖:DDD(領域驅動設計)分層架構),各層都有不同的服務,但由於各層職責不一樣,服務目的和實現方式也存在差異。

1、應用層服務

應用層是很瘦的一層,其服務主要用來表述應用和使用者行為。它主要負責服務的組合、編排和轉發,負責處理業務用例的執行順序以及結果的拼裝,拼裝完領域服務後以粗粒度的服務通過API閘道器向前臺應用釋出。通過這樣一種方式,隱藏了領域層的複雜性及其內部實現機制。 應用層除了定義應用服務之外,在這層還可以進行安全認證,許可權校驗,持久化事務控制或向其他系統傳送基於事件的訊息通知。

2、領域層服務

領域層是較“胖”的一層,它實現了全部業務邏輯並且通過各種校驗手段保證業務正確性。業務邏輯包括:業務流程、業務策略、業務規則、完整性約束等。 當領域中的某個操作過程或轉換過程不是實體或值物件的職責時,便將該操作放在一個單獨的服務介面中,這就是領域服務,領域服務是無狀態的。

3、基礎設施層服務
 
基礎設施層服務位於基礎設施層,根據依賴倒置原則,封裝基礎資源服務,實現資源層與應用層和領域層的呼叫依賴反轉,為應用層和領域層提供基礎資源服務(如資料庫、快取等基礎資源),實現各層的解耦,降低外部資源的變化對核心業務邏輯的影響。

4、總結

應用層服務是展現層和領域層的橋樑,通過呼叫領域物件和領域層服務來表達用例和使用者故事。領域物件負責單一操作, 領域層服務用於協調多個領域物件共同完成某個業務操作。 應用服務原則上不處理業務邏輯,領域服務處理業務邏輯。

微服務的邊界設計

邏輯邊界與物理邊界

在領域模型設計時,我們通常會根據限界上下文將領域分解成不同的子域,劃分業務領域的邏輯邊界。在限界上下文內不同的實體和值物件可以組合成不同的聚合,從而形成聚合與聚合之間的邏輯邊界。一般來說,限界上下文可以作為微服務拆分的依據,而限界上下文內的聚合由於其業務邏輯的高度內聚,也可以根據需要將同一領域內的聚合業務邏輯程式碼拆分為微服務,聚合是領域中可以拆分為微服務的最小單元。

限界上下文與限界上下文之間以及聚合與聚合之間的邊界是邏輯邊界,微服務與微服務的邊界是物理邊界。邏輯邊界強調業務領域邏輯或程式碼分層的隔離,物理邊界強調部署和執行的隔離。

微服務設計時是否一定要做到邏輯邊界與物理邊界一致?

邏輯邊界的劃分是否可以細於物理邊界?

過度的微服務拆分會導致服務、安全和運維管理更復雜,領域之間的服務協同或應用層的處理邏輯更復雜,總之一句話就是:需要更高的研發技能要求和軟體維護成本。因此領域和程式碼分層的邏輯邊界的細分是必要的,但是物理邊界不宜過細,也就是說在不違反微服務拆分原則的情況下,不宜過度拆分微服務。

為什麼要細分業務和程式碼邏輯邊界?

在從單體向微服務演進後,隨著新需求的出現,新的微服務會開始慢慢的膨脹起來,有一天你會發現膨脹的微服務有一部分業務能力需要拆分出去時,如果沒有提前進行邏輯邊界的細分,微服務內程式碼的過度耦合將會讓你無從下手,你是否還需要再做一次從單體向微服務的拆分?

如果你在微服務設計時已經根據業務領域邊界提前進行了領域程式碼的分層和邏輯隔離,在微服務再次拆分時,分別對邏輯分離的領域程式碼打包,同步進行資料庫拆分,就可以快速完成微服務的拆分,而不需要重複從單體應用向微服務痛苦的演進過程。

當然,在同一個微服務內邏輯隔離的程式碼,在內部領域服務之間呼叫以及資料訪問設計上需要有合理的鬆耦合的設計和開發規範,否則也不能很快的完成微服務再次拆分。

總之,我們需要內外部邏輯邊界清晰的微服務,而不是從一個大單體重構為多個小單體。

要做微服務而不是小單體

很多時候大家對微服務設計的理解都以為只要最後確定拆分出多少個微服務就可以了,其實拆成多少個微服務並不是微服務架構的要點。如何設計或拆分才能避免拆分出來的微服務不是小單體?這才是所有微服務架構團隊需要關注和解決的問題,這也是DDD的價值所在。

\"9\"

要做微服務而不是小單體

評判微服務設計合理的一個簡單標準就是:微服務在隨著業務發展而不斷拆分或者重新組合過程中不會過度增加軟體維護成本,並且這個過程是非常輕鬆且簡單的。

微服務程式碼邏輯分層和結構

為了方便在微服務變大時實現快樂的拆分和合並,在明確各層程式碼職責後,我們需要對微服務程式碼合理分層和邏輯隔離,以下圖為例對程式碼分層和結構進行簡要說明。

基礎層程式碼:本層主要包括兩類適配程式碼:主動適配和被動適配。主動適配程式碼主要面向前端應用提供API閘道器服務,進行簡單的前端資料校驗、協議以及格式轉換適配等工作。被動適配主要面向後端基礎資源(如資料庫、快取等),通過依賴反轉為應用層和領域層提供資料持久化和資料訪問支援,實現資源層的解耦。

應用層程式碼:本層程式碼主要通過呼叫領域層服務或其他中臺應用層服務,完成服務組合和編排形成粗粒度的服務,為前臺提供API服務。本層程式碼可進行業務邏輯資料的校驗、許可權認證、服務組合和編排、分散式事務管理等工作。

領域層程式碼:本層程式碼主要實現核心的業務領域邏輯,需要做好領域程式碼的分層以及聚合之間程式碼的邏輯隔離。相關的開發方法請查閱DDD戰術設計相關資料,並遵循相關設計和開發規範。

\"10\"

程式碼邏輯分層和結構

對程式碼進行邏輯隔離和分層的主要意義在於:

1、避免各層程式碼的交叉,保持領域程式碼的純潔,保證中臺領域層業務邏輯的穩定。

2、業務和程式碼模型的邏輯保持一致,有利於微服務的拆分和組合。

微服務的設計和拆分

微服務拆分方法

絞殺者模式

絞殺者模式類似建築拆遷,在新建築分階段建設完成入住後,分步拆除舊建築物。

“絞殺者模式”是在遺留系統外圍,將新功能用新的方式構建為新的服務 。通過在新的應⽤中實現新特性,保持和現有系統的鬆耦合,隨著時間的推移,新的服務逐漸“絞殺”老的系統。以此逐步地替換原有系統。 對於那些老舊龐大難以更改的遺留系統,推薦採用絞殺者模式。

修繕者模式

修繕者模式類似文物修復,將存在問題的部分建築重建或者修復後,重新加入到原有的建築中,保持建築原貌。

“修繕者模式”是在既有系統的基礎上,通過剝離新業務和功能,逐步“釋放”現有系統耦合度,解決遺留系統質量不穩定和Bug多的問題。就如修房或修路一樣,將老舊待修繕的部分進行隔離,用新的方式對其進行單獨修復。 修復的同時,需保證與其他部分仍能協同功能。 修繕模式適用於需求變更頻率不高的存量系統。

微服務拆分原則

微服務拆分過程中需嚴格遵守高內聚、低耦合原則,同時結合專案的實際情況,綜合考慮業務領域、功能穩定性、應用效能、團隊以及技術等因素。

1、基於業務領域拆分,在領域模型設計時需對齊限界上下⽂,圍繞業務領域按職責單一性、功能完整性進行拆分,避免過度拆分造成跨微服務的頻繁呼叫。

2、基於業務變化頻率和業務關聯拆分,識別系統中的業務需求變動較頻繁的功能,考慮業務變更頻率與相關度,並對其進行拆分,降低敏態業務功能對穩態業務功能的影響。

3、基於應用效能拆分,考慮系統⾮功能性需求,識別系統中效能壓力較大的模組,並優先對其進行拆分,提升整體效能,縮小潛在效能瓶頸模組的影響範圍。

4、基於組織架構和團隊規模,提高團隊溝通效率。

5、基於軟體包大小,軟體包過大,不利用微服務的彈性伸縮。

6、基於不同功能的技術和架構異構以及系統複雜度。

分散式架構設計的關注點

企業一旦採用分散式架構和微服務技術體系,在設計時需要關注商業模式、業務邊界、資料體系、微服務設計、前臺互動以及多活容災等多領域的協同。

1、資料是本難唸的經

分散式架構下資料面臨的問題遠比集中式架構複雜。諸如:分散式資料庫的選型、資料的分庫和分表、資料的同步與非同步、跨庫和聯表查詢、資料的分佈與集中、線上業務資料與統計分析資料的協同、集中式資料庫向分散式資料庫的遷移以及面向場景的集中資料複製等。

(1)分散式資料庫的選擇

從集中式架構向分散式架構轉型,第一步就需要考慮選擇什麼樣的分散式資料庫。說到這裡我也給大家推薦一個架構學習交流圈。交流學習企鵝圈號:519752913,裡面會分享一些資深架構師錄製的視訊錄影:有Spring,MyBatis,Netty原始碼分析,高併發、高效能、分散式、微服務架構的原理,JVM效能優化、分散式架構等這些成為架構師必備的知識體系。還能領取免費的學習資源,目前受益良多

為解決交易型分散式資料庫的橫向計算能力,目前主要有三種類型的分散式資料庫:一體化交易型分散式資料庫方案(如阿里OceanBase和華為高斯資料庫,多采用Paxos協議實現多副本資料一致)、單機交易資料庫加資料庫中介軟體方案(如騰訊TDSQL和TBase等,多采用資料同步實現多副本資料一致)和單機交易資料庫加分庫基礎類庫(如ShardingSphere等,主要實現資料路由和歸集)方案。三者的使用場景基本相同,都是通過對大表資料作水平切分,業務請求動態路由到指定節點,以此達到計算能力的線性擴充套件。一體化方案是以資料庫和中介軟體一體化產品的形式解決線性擴充套件問題,支援多副本,高可用,提供統一的運維介面。 資料庫中介軟體方案是以獨立資料庫中介軟體結合集中式資料庫的方式來解決線性擴充套件問題,高可用功能由中介軟體和資料庫自身功能分別保證。分庫基礎類庫方案是一種類似中介軟體的輕量級解決方案,適合簡單快速的交易操作,在強一致性和聚合分析查詢方面較弱。

(2)資料的分庫和分庫主鍵

選擇完分散式資料庫後,第二步就需要考慮如何按照領域模型和微服務進行資料庫的分庫設計,選擇合適的分庫主鍵將是一個關鍵技術點。

對於與客戶接觸的業務領域,個人認為可以以客戶維度作為資料分庫主鍵,以客戶為實體,確保所有與本客戶接觸和服務的資料都在一個單元內,通過集中共享的中臺服務,為所有渠道的客戶提供一致性體驗。如果後序管理流程需要基於區域管理要求,也可以考慮在後序業務環節的資料庫中以區域維度作為資料庫分庫主鍵,滿足業務基於區域的管理要求。

如何將客戶維度的資料傳輸到以區域為維度的資料庫中?我們可以考慮基於訊息佇列的事件驅動模型。

系統如果做不到“以客戶為中心”,又如何能實現“以客戶為中心”的業務需求呢?

(3)高頻熱點資料的快取

對於像產品基礎資料、主資料之類的熱點高頻訪問資料,在進行系統設計時需考慮將這些資料載入至快取中,降低資料庫的壓力,對外提供高效能的資料訪問能力。

快取技術的使用就像調味料一樣,投入小見效快,使用者體驗提升快。

(4)資料副本與跨庫聯表查詢

採用分散式技術後,資料將碎片化,為了減輕由於跨庫以及聯表查詢給分散式資料庫的壓力,需要建立多維的全域性資料檢視(如客戶統一檢視、業務統計資料檢視等)和麵向具體場景的預處理好的資料聚合副本,提供複雜場景的資料查詢服務,減輕交易型資料庫的壓力。

全域性資料檢視其資料來源於各業務條線的分散式資料庫,從源端分散式資料庫通過準實時的方式彙集(可以基於資料庫日誌捕獲技術加訊息佇列)。全域性檢視的資料庫也可以是分散式資料庫,根據業務要求選擇合適的分庫主鍵進行資料重分佈。

對於分散式資料庫跨庫關聯查詢效能低的問題,有兩種解決方案,根據具體場景採用合適的方案:

1)面向場景的資料副本查詢庫。將這些需要關聯查詢的資料副本集中存放在一個分散式資料庫中。在進行資料彙集時,提前做好資料關聯處理(如多表資料合併成一個寬表),通過查詢微服務,專職提供關聯查詢服務。

2)小表廣播模式。有些業務場景中少量表(如使用者、機構表等)需要跟業務資料進行關聯查詢,這種場景可以考慮在業務資料庫中新建一張複製表(無需全部欄位,取必要欄位即可),在主表發生變化時,可以通過釋出訂閱的訊息佇列模式重新整理複製表的資料,保證資料的一致性。

(5)合理的資料冗餘

完成領域模型和微服務設計後,集中式資料庫的資料將被分散到不同微服務的分散式資料庫中。資料實體的依賴關係將被打破,如果需要呼叫前序或後序微服務的資料實體(如:投保微服務生成的投保單、保單管理微服務的保單需要關聯投保單,理賠的報案需要關聯保單等,或電商業務中:銷售過程中的商品、運輸過程中的貨物需要關聯商品資訊),這時候就會跨庫或者跨微服務呼叫了,必然影響系統性能。

如何處理這些跨微服務的關鍵實體資料?

最好的方式就是資料冗餘,將前序或後序環節的關鍵資料以資料清單複製表(只需必要的關鍵資料,不需要所有明細資料)的方式冗餘儲存。冗餘的好處是,前臺頁面可以一次性獲取本領域實體資料和關聯實體清單資料,同時也可以在本庫對關聯清單資料進行查詢。只有在需要獲取關聯實體資料明細時,才呼叫前序或後續微服務獲取全量資料。

合理的資料冗餘可以減少跨庫查詢,提升系統性能。

(6)如何資料遷移?

從集中式資料庫向分散式資料庫切換時,資料遷移的複雜度將大大增加。需要考慮如何進行資料遷移?現有技術條件下,是不是不做資料遷移也可以無縫切換?

傳統集中式架構資料多集中在一個集中式資料庫中,資料關聯度高。

分散式架構下,資料會隨著微服務而同步拆分,資料將變得碎片化,存在複製表,資料重分佈,資料關聯被打破,甚至還可能需要重建資料關聯。另外,分散式架構的容災和多中心多活要求,資料遷移時還需要考慮資料的多副本和多中心的資料複製。分散式架構下資料遷移的複雜度大增。

網際網路公司大多采用演進式架構模式,有計劃分階段的進行技術體系的升級,很多時候使用者無感知就完成了架構的升級。而傳統企業在做技術升級時如採用絞殺者重構模式,是否必須要做資料遷移?如果不做資料遷移是否也可以順利切換?是否通過資料路由加全量資料檢視的方案就可以不做資料遷移,實現新舊並存,無縫切換?資料切換方案需要詳細設計和慎重考慮(尚在考慮中,且聽下回分解)。

(7)資料的非同步和同步

分散式架構下事件驅動設計模式是常用的方法,通過基於訊息佇列的釋出訂閱模式,可以很好的實現業務非同步化。非實時業務場景可以採用事件驅動的模式實現非同步化,減輕資料庫壓力。

也可以通過非同步模式實現準實時的資料讀寫分離,提高資料庫效能。

2、中臺和微服務要處理好邊界

條條道路通羅馬,不管走哪條路,憑感覺或拍腦袋也可以設計出微服務,拆分結果可能與按照DDD方法出來的結果類似。但是如果有好的理論和方法指導,不但做事情有矩可循的,而且可以避免走彎路。由於DDD在設計的時候已經做好了邏輯的邊界劃分,在微服務需要組合和重新拆分時也會變得容易得多。

還是有必要提一下:中臺和微服務設計可以借鑑DDD的設計原則和理念,不過戰術設計部分由於過於複雜和學習成本過高,可以參考使用。

3、前、中臺協同和前臺資料的按需載入

前臺應用未來可能多采用單頁面(SPA)的微前端(對應於微服務的前端展現,一個微服務對應一個微前端)方式,通過前端整合框架(類似門戶)實現多頁面組合,提供統一的使用者體驗,在微服務和資料庫設計時也需要協同考慮前端頁面邏輯。

為減輕跨微服務的訪問,前端頁面展示時應以清單資料方式按需載入,後端資料設計時也應同步考慮如何組合前端資料展示。如需要展示明細資料,通過呼叫API服務的方式獲取全量資料,減少不必要的跨微服務呼叫。

另外,符合條件的應用也可考慮頁面的動靜分離和路由接入,將靜態頁面通過CDN的技術,部署在靠近使用者的機房,降低互動次數,減少跨廣域網訪問帶來的網路延遲。

前端知識有限,就寫這麼多了,哈哈。

4、容災和多活的全域性考慮

分散式架構的高可用是在應用、資料和基礎設施的分散式技術升級後,通過多資料中心協同來實現的。

為了容災和多活,在設計方面需要考慮:1)合適的分散式資料庫。2)合理的資料分庫主鍵設計,資料的多副本和同步技術。3)單元化架構設計,處理好通用中臺和專屬中臺的部署和依賴關係,實現業務的自包含,減少跨資料中心呼叫。4)訪問層的接入,對外部訪問進行路由、限流以及灰度釋出。5)統一的全域性配置資料,每個資料中心都有實時同步的全量配置資料,實現容災和多活的一鍵切換。

5、避免過度拆分和硬體依賴

過度過細的微服務拆分帶來更多的軟體維護成本和運維壓力,過多的分散式事務也會帶來效能和資料一致性的壓力。在進行設計時,要在保證邏輯邊界清晰的情況下,嚴控微服務的過度拆分和採用過多的分散式事務。

分散式架構的自動的彈性伸縮大多是通過軟體的方式去實現的,為保證應用的彈性伸縮能力,在設計中應實現去硬體的無中心化(如可採用軟負載,就不用F5之類的硬負載),儘量通過軟體實現彈性伸縮。因為一旦繫結硬體裝置,在硬體遇到瓶頸需要自動彈性伸縮的時候,就需要人工干預,無法自動彈性伸縮。

寫在最後

正如老馬說的採用微服務的企業需具備一定的高度,如文化、組織和技術,DDD同樣也需要站一定的高度。如果高度不夠,我們是否可以站在巨人的肩上呢?

在領域模型和微服務設計時,守住領域模型和邊界,各司其職,才能長治久安!

謹記:邊界