1. 程式人生 > >基於DDD的微服務設計和開發實戰

基於DDD的微服務設計和開發實戰

你是否還在為微服務應該拆多小而爭論不休?到底如何才能設計出收放自如的微服務?怎樣才能保證業務領域模型與程式碼模型的一致性?或許本文能幫你找到答案。 本文是基於 DDD 的微服務設計和開發實戰篇,通過借鑑領域驅動設計思想,指導微服務專案團隊進行設計和開發(理論篇詳見《當中臺遇上 DDD,我們該如何設計微服務?》)。本文包括三部分內容:第一部分講述領域驅動設計基本知識,包括:分層架構、服務檢視、資料檢視和領域事件釋出和訂閱等;第二部分講述微服務設計方法、過程、模板、程式碼目錄、設計原則等內容;最後部分以一個專案為例講述基於 DDD 的微服務設計過程。

目標¶

本文采用 DDD(領域驅動設計)作為微服務設計指導思想,通過事件風暴建立領域模型,合理劃分領域邏輯和物理邊界,建立領域物件及服務矩陣和服務架構圖,定義符合 DDD 分層架構思想的程式碼結構模型,保證業務模型與程式碼模型的一致性。通過上述設計思想、方法和過程,指導團隊按照 DDD 設計思想完成微服務設計和開發。 通過領域模型和 DDD 的分層思想,遮蔽外部變化對領域邏輯的影響,確保交付的軟體產品是邊界清晰的微服務,而不是內部邊界依然混亂的小單體。在需求和設計變化時,可以輕鬆的完成微服務的開發、拆分和組合,確保微服務不易受外部變化的影響,並穩定執行。

適用範圍¶

本文適用於按照 DDD 設計方法進行微服務設計和開發的專案及相關人員。

DDD 分層架構檢視¶

DDD 分層架構包括:展現層、應用層、領域層和基礎層。

DDD 分層架構各層職能如下:

  • 展現層
    展現層負責向用戶顯示資訊和解釋使用者指令。
  • 應用層
    應用層是很薄的一層,主要面向使用者用例操作,協調和指揮領域物件來完成業務邏輯。應用層也是與其他系統的應用層進行互動的必要渠道。應用層服務儘量簡單,它不包含業務規則或知識,只為下一層的領域物件協調任務,使它們互相協作。應用層還可進行安全認證、許可權校驗、分散式和持久化事務控制或向外部應用傳送基於事件的訊息等。
  • 領域層
    領域層是軟體的核心所在,它實現全部業務邏輯並且通過各種校驗手段保證業務正確性。它包含業務所涉及的領域物件(實體、值物件)、領域服務以及它們之間的關係。它負責表達業務概念、業務狀態以及業務規則,具體表現形式就是領域模型。
  • 基礎層
    基礎層為各層提供通用的技術能力,包括:為應用層傳遞訊息、提供 API 管理,為領域層提供資料庫持久化機制等。它還能通過技術框架來支援各層之間的互動。

服務檢視¶

微服務內的服務檢視

微服務內有 Facade 介面、應用服務、領域服務和基礎服務,各層服務協同配合,為外部提供服務。

1、介面服務¶

介面服務位於使用者介面層,用於處理使用者傳送的 Restful 請求和解析使用者輸入的配置檔案等,並將資訊傳遞給應用層。

2、應用服務¶

應用服務位於應用層。用來表述應用和使用者行為,負責服務的組合、編排和轉發,負責處理業務用例的執行順序以及結果的拼裝。 應用層的服務包括應用服務和領域事件相關服務。 應用服務可對微服務內的領域服務以及微服務外的應用服務進行組合和編排,或者對基礎層如檔案、快取等資料直接操作形成應用服務,對外提供粗粒度的服務。 領域事件服務包括兩類:領域事件的釋出和訂閱。通過事件匯流排和訊息佇列實現非同步資料傳輸,實現微服務之間的解耦。

3、領域服務¶

領域服務位於領域層,為完成領域中跨實體或值物件的操作轉換而封裝的服務,領域服務以與實體和值物件相同的方式參與實施過程。 領域服務對同一個實體的一個或多個方法進行組合和封裝,或對多個不同實體的操作進行組合或編排,對外暴露成領域服務。領域服務封裝了核心的業務邏輯。實體自身的行為在實體類內部實現,向上封裝成領域服務暴露。 為隱藏領域層的業務邏輯實現,所有領域方法和服務等均須通過領域服務對外暴露。 為實現微服務內聚合之間的解耦,原則上禁止跨聚合的領域服務呼叫和跨聚合的資料相互關聯。

4、基礎服務¶

基礎服務位於基礎層。為各層提供資源服務(如資料庫、快取等),實現各層的解耦,降低外部資源變化對業務邏輯的影響。 基礎服務主要為倉儲服務,通過依賴反轉的方式為各層提供基礎資源服務,領域服務和應用服務呼叫倉儲服務介面,利用倉儲實現持久化資料物件或直接訪問基礎資源。

微服務外的服務檢視¶

1. 前端應用與微服務¶

微服務中的應用服務通過使用者介面層組裝和資料轉換後,釋出在 API 閘道器,為前端應用提供資料展示服務。

2. 微服務與外部應用¶

跨微服務資料處理時,對實時性要求高的場景,可選擇直接呼叫應用服務的方式(新增和修改型別操作需關注事務一致性)。對實時性要求不高的場景,可選擇非同步化的領域事件驅動機制(最終資料一致性)。

資料檢視¶

DDD 分層架構中資料物件轉換的過程如下圖。

資料檢視應用服務通過資料傳輸物件(DTO)完成外部資料交換。領域層通過領域物件(DO)作為領域實體和值物件的資料和行為載體。基礎層利用持久化物件(PO)完成資料庫的交換。 DTO 與 VO 通過 Restful 協議實現 JSON 格式和物件轉換。 前端應用與應用層之間 DTO 與 DO 的轉換髮生在使用者介面層。如微服務內應用服務需呼叫外部微服務的應用服務,則 DTO 的組裝和 DTO 與 DO 的轉換髮生在應用層。 領域層 DO 與 PO 的轉換髮生在基礎層。

領域事件和事件匯流排¶

領域事件是領域模型中非常重要的部分,用來表示領域中發生的事件。一個領域事件將導致進一步的業務操作,有助於形成完整的業務閉環。領域事件主要用於解耦微服務,各個微服務之間不再是強一致性,而是基於事件的最終一致性。

微服務內的領域事件¶

微服務內的領域事件可以通過事件匯流排或利用應用服務實現不同聚合之間的業務協同。當微服務內發生領域事件時,由於大部分事件的整合發生在同一個執行緒內,不一定需要引入訊息中介軟體。但一個事件如果同時更新多個聚合資料,按照 DDD“一個事務只更新一個聚合根”的原則,可以考慮引入訊息中介軟體,通過非同步化的方式,對微服務內不同的聚合根採用不同的事務。

微服務之間的領域事件¶

微服務之間的資料互動方式通常有兩種:應用服務呼叫和領域事件驅動機制。 領域事件驅動機制更多的用於不同微服務之間的整合,實現微服務之間的解耦。事件庫(表)可以用於微服務之間的資料對賬,在應用、網路等出現問題後,可以實現源和目的端的資料比對,在資料暫時不一致的情況下仍可根據這些資料完成後續業務處理流程,保證微服務之間資料的最終一致性。 應用服務呼叫方式通常應用於實時性要求高的業務場景,但一旦涉及到跨微服務的資料修改,將會增加分散式事務控制成本,影響系統性能,微服務之間的耦合度也會變高。

事件匯流排¶

事件匯流排位於基礎層,為應用層和領域層服務提供事件訊息接收和分發等服務。其大致流程如下: 1、服務觸發併發布事件。 2、事件匯流排事件分發。

  • 如果是微服務內的訂閱者(微服務內的其它聚合),則直接分發到指定訂閱者。
  • 如果是微服務外的訂閱者,則事件訊息先儲存到事件庫(表)並非同步傳送到訊息中介軟體。
  • 如果同時存在微服務內和外訂閱者,則分發到內部訂閱者,並將事件訊息儲存到事件庫(表)並非同步傳送到訊息中介軟體。為了保證事務的一致性,事件表可以共享業務資料庫。也可以採用多個微服務共享事件庫的方式。當業務操作和事件釋出操作跨資料庫時,須保證業務操作和事件釋出操作資料的強一致性。

事件資料持久化¶

事件資料的持久化儲存可以有兩種方案,在專案實施過程中根據具體場景選擇最佳方案。

  • 事件資料儲存到微服務所在業務資料庫的事件表中,利用本地事務保證業務操作和事件釋出操作的強一致性。
  • 事件資料儲存到多個微服務共享的事件庫中。需要注意的一點是:這時業務操作和事件釋出操作會跨資料庫操作,須保證事務的強一致性(如分散式事務機制)。

事件資料的持久化可以保證資料的完整性,基於這些資料可以完成跨微服務資料的一致性比對。

微服務設計方法¶

事件風暴¶

本階段主要完成領域模型設計。 基於 DDD 的微服務設計通常採用事件風暴方法。通過事件風暴完成領域模型設計,劃分出微服務邏輯邊界和物理邊界,定義領域模型中的領域物件,指導微服務設計和開發。事件風暴通常包括產品願景、場景分析、領域建模、微服務設計和拆分等過程。本文不對事件風暴詳細方法做深入描述,如感興趣可查閱相關資料。

  • 1、產品願景

產品願景是對產品的頂層價值設計,對產品目標使用者、核心價值、差異化競爭點等資訊達成一致,避免產品偏離方向。建議參與角色:業務需求方、產品經理和開發組長。

  • 2、場景分析

場景分析是從使用者視角出發,探索業務領域中的典型場景,產出領域中需要支撐的場景分類、用例操作以及不同子域之間的依賴關係,用以支撐領域建模。 建議參與角色:產品經理、需求分析人員、架構師、開發組長和測試組長。

  • 3、領域建模

領域建模是通過對業務和問題域進行分析,建立領域模型,向上通過限界上下文指導微服務邊界設計,向下通過聚合指導實體的物件設計。 建議參與角色:領域專家、產品經理、需求分析人員、架構師、開發組長和測試組長。

  • 4、微服務拆分和設計

結合業務限界上下文與技術因素,對服務的粒度、分層、邊界劃分、依賴關係和整合關係進行梳理,完成微服務拆分和設計。 微服務設計應綜合考慮業務職責單一、敏態與穩態業務分離、非功能性需求(如彈性伸縮要求、安全性等要求)、團隊組織和溝通效率、軟體包大小以及技術異構等因素。 建議參與角色:產品經理、需求分析人員、架構師、開發組長和測試組長。

領域物件及服務矩陣和程式碼模型設計¶

本階段完成領域物件及服務矩陣文件以及微服務程式碼模型設計。

  • 1、領域物件及服務矩陣

根據事件風暴過程領域物件和關係,對產出的限界上下文、聚合、實體、值物件、倉儲、事件、應用服務、領域服務等領域物件以及各物件之間的依賴關係進行梳理,確定各物件在分層架構中的位置和依賴關係,建立領域物件分層架構檢視,為每個領域物件建立與程式碼模型物件的一一對映。 建議參與角色:架構師和開發組長。

  • 2、微服務程式碼模型

根據領域物件在 DDD 分層架構中所在的層、領域型別、與程式碼物件的對映關係,定義領域物件在微服務程式碼模型中的包、類和方法名稱等,設計微服務工程的程式碼層級和程式碼結構,明確各層間的呼叫關係。 建議參與角色:架構師和開發組長。

領域物件及服務矩陣樣例說明¶

領域物件及服務矩陣主要用來記錄事件風暴和微服務設計過程中產出的領域物件屬性,如:各領域物件在 DDD 分層架構中的位置、屬性、依賴關係以及與程式碼物件的對映關係等。通過建立領域物件與程式碼物件的對映關係,可指導軟體開發人員準確無誤的按照設計文件完成微服務開發。 以下為領域物件及服務矩陣樣例(部分資料,僅供參考)。

各欄說明如下: * 層:

定義領域物件位於 DDD 分層架構中的哪一層。如:介面層、應用層、領域層以及基礎層等。

  • 聚合:

在事件風暴過程中將關聯緊密的實體和值物件等組合形成聚合。本欄說明聚合名稱。 * 領域物件名稱:

領域模型中領域物件的具體名稱。如:“請假審批已通過”是型別為“事件”的領域物件;“請假單”是領域型別為“實體”的領域物件。

  • 領域型別:

在領域模型中根據 DDD 知識域定義的領域物件的型別,如:限界上下文、聚合、聚合根(實體)、實體、值物件、事件、命令、應用服務、領域服務和倉儲服務等。 依賴物件名稱:根據業務物件依賴或分層呼叫依賴關係建立的領域物件的依賴關係(如服務呼叫依賴、關聯物件聚合等)。本欄說明領域物件需依賴的其他領域物件,如上層服務在組合和編排過程中對下層服務的呼叫依賴、實體之間或者實體與值物件在聚合內的依賴等。

  • 包名:

程式碼模型中的包名,本欄說明領域物件所在的軟體包。

  • 類名:

程式碼模型中的類名,本欄說明領域物件的類名。

  • 方法名:

程式碼模型中的方法名,本欄說明領域物件實現或操作的方法名。

微服務程式碼結構模型¶

微服務程式碼模型最終結果來源於領域物件及服務矩陣。在程式碼模型設計時須建立領域物件和程式碼物件的一一對映,保證業務模型與程式碼模型的一致性,即使不熟悉業務的開發人員或者不熟悉程式碼的業務人員也可以很快定位到程式碼位置。

微服務程式碼總目錄¶

基於 DDD 的程式碼模型包括 interfaces、application、domain 和 infrastructure 四個目錄。

  • Interfaces(使用者介面層):

本目錄主要存放使用者介面層程式碼。前端應用通過本層嚮應用服務獲取展現所需的資料。本層主要用於處理使用者傳送的 Restful 請求和解析使用者輸入的配置檔案等,並將資訊傳遞給 Application 層。主要程式碼形態是資料組裝以及 Facade 介面等。

  • Application(應用層):

本目錄主要存放應用層程式碼。應用服務程式碼基於微服務內的領域服務或微服務外的應用服務完成服務編排和組合。為使用者介面層提供各種應用資料展現支援。主要程式碼形態是應用服務和領域事件等。

  • Domain(領域層):

本目錄主要存放領域層程式碼。本層程式碼主要實現核心領域邏輯,其主要程式碼形態是實體類方法和領域服務等。

  • Infrastructure(基礎層):

本目錄存放基礎層程式碼,為其它各層提供通用技術能力、三方軟體包、配置和基礎資源服務等。

使用者介面層程式碼模型¶

使用者介面層程式碼模型目錄包括:assembler、dto 和 facade。

  • Assembler:實現 DTO 與領域物件之間的相互轉換和資料交換。理論上 Assembler 總是與 DTO 一同被使用。

  • Dto:資料傳輸的載體,內部不存在任何業務邏輯,通過 DTO 把內部的領域物件與外界隔離。

  • Facade:提供較粗粒度的呼叫介面,將使用者請求委派給一個或多個應用服務進行處理。

應用層程式碼模型¶

應用層程式碼模型目錄包括:event 和 service。

  • Event(事件):事件目錄包括兩個子目錄:publish 和 subscribe。publish 目錄主要存放微服務內領域事件釋出相關程式碼。subscribe 目錄主要存放微服務內聚合之間或外部微服務領域事件訂閱處理相關程式碼。為了實現領域事件的統一管理,微服務內所有領域事件(包括應用層和領域層事件)的釋出和訂閱處理都統一放在應用層。

  • Service(應用服務):這裡的服務是應用服務。應用服務對多個領域服務或外部應用服務進行封裝、編排和組合,對外提供粗粒度的服務。

領域層程式碼模型¶

微服務領域層包括一個或多個聚合程式碼包。標準的聚合程式碼模型包括:entity、repository 和 service 三個子目錄。

  • Aggregate(聚合):聚合程式碼包的根目錄,實際專案中以實際業務屬性的名稱來命名。聚合定義了領域物件之間的關係和邊界,實現領域模型的內聚。
  • Entity(實體):存放實體(含聚合根、實體和值物件)相關程式碼。同一實體所有相關的程式碼(含對同一實體類多個物件操作的方法,如對多個物件的 count 等)都放在一個實體類中。
  • Service(領域服務):存放對多個不同實體物件操作的領域服務程式碼。這部分程式碼以領域服務的形式存在,在設計時一個領域服務對應一個類。
  • Repository(倉儲):存放聚合對應的查詢或持久化領域物件的程式碼,通常包括倉儲介面和倉儲實現方法。為了方便聚合的拆分和組合,我們設定一個原則:一個聚合對應一個倉儲。
  • 特別說明:按照 DDD 分層原則,倉儲實現本應屬於基礎層程式碼,但為了微服務程式碼拆分和重組的便利性,我們把聚合的倉儲實現程式碼放到了領域層對應的聚合程式碼包內。如果需求或者設計發生變化導致聚合需要拆分或重新組合時,我們可以聚合程式碼包為單位,輕鬆實現微服務聚合的拆分和組合。

基礎層程式碼模型¶

基礎層程式碼模型包括:config 和 util 兩個子目錄。

  • Config:主要存放配置相關程式碼。
  • Util:主要存放平臺、開發框架、訊息、資料庫、快取、檔案、匯流排、閘道器、第三方類庫、通用演算法等基礎程式碼,可為不同的資源類別建立不同的子目錄。

微服務總目錄結構¶

微服務總目錄結構如下:

微服務設計原則¶

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

  • 第一條:“要領域驅動設計,而不是資料驅動設計,也不是介面驅動設計”。

微服務設計首先應建立領域模型,確定邏輯和物理邊界後,然後才進行微服務邊界拆分,而不是一上來就定義資料庫表結構,也不是介面需要什麼,就去調整領域邏輯程式碼。 領域模型和領域服務應具有高度通用性,通過介面層和應用層遮蔽外部變化對業務邏輯的影響,保證核心業務功能的穩定性。

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

微服務完成開發後其功能和程式碼也不是一成不變的。隨著需求或設計變化,微服務內的程式碼也會分分合合。邏輯邊界清晰的微服務,可快速實現微服務程式碼的拆分和組合。DDD 思想中的邏輯邊界和分層設計也是為微服務各種可能的分分合合做準備的。 微服務內聚合與聚合之間的領域服務以及資料原則上禁止相互產生依賴。如有必要可通過上層的應用服務編排或者事件驅動機制實現聚合之間的解耦,以利於聚合之間的組合和拆分。

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

分層架構中各層職能定位清晰,且都只能與其下方的層發生依賴,也就是說只能從外層呼叫內層服務,內層服務通過封裝、組合或編排對外逐層暴露,服務粒度由細到粗。 應用層負責服務的編排和組合,領域層負責領域業務邏輯的實現,基礎層為各層提供資源服務。

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

微服務的過度拆分必然會帶來軟體維護成本的上升,如:整合成本、運維成本以及監控和定位問題的成本。企業轉型過程中很難短時間內提升這些能力,如果專案團隊不具備這些能力,將很難 hold 住這些過細的微服務。而如果我們在微服務設計之初就已經定義好了微服務內的邏輯邊界,專案初期我們可以儘可能少的拆分出過細的微服務,隨著技術的積累和時間的推移,當我們具有這些能力後,由於微服務內有清晰的邏輯邊界,這時就可以隨時根據需要輕鬆的拆分或組合出新的微服務。

不同場景的微服務設計¶

微服務的設計先從領域建模開始,領域模型是微服務設計的核心,微服務是領域建模的結果。在微服務設計之前,請先判斷你的業務是否聚焦在領域和領域邏輯。 實際在做系統設計時我們可能面臨各種不同的情形,如從傳統單體拆分為多個微服務,也可能是一個全新領域的微服務設計(如創業中的應用),抑或是將一個單體中面臨問題或效能瓶頸的模組拆分為微服務而其餘功能仍為單體的情況。 下面分幾類不同場景說明如何進行微服務和領域模型設計。

新建系統的微服務設計¶

新建系統會遇到複雜和簡單領域兩種場景,兩者的領域建模過程也會有所差別。

1、簡單領域的建模¶

對於簡單的業務領域,一個領域可能就是一個小的子域。領域建模過程相對簡單,根據事件風暴可以分解出事件、命令、實體、聚合和限界上下文等,根據領域模型和微服務拆分原則設計出微服務即可。

2、複雜領域的建模¶

對於複雜的業務領域,領域可能還需要拆分為子域,甚至子域還會進一步拆分,如:保險領域可以拆分為承保、理賠、收付費和再保等子域,承保子域還可以再拆分為投保、保單管理等子子域。對於這種複雜的領域模型,是無法通過一個事件風暴完成領域建模的,即使能完成,其工程量也是非常浩大,效果也不一定好。 對於這種複雜的領域,我們可以分三階段來完成領域模型和微服務設計。

  • 拆分子域建立領域模型:根據業務特點考慮流程節點或功能模組等邊界因素(微服務最終的拆分結果很多時候跟這些邊界因素有一定的相關性),按領域逐級分解為大小合適的子域,針對子域進行事件風暴,記錄領域物件、聚合和限界上下文,初步確定各級子域的領域模型。

  • 領域模型微調:梳理領域內所有子域的領域模型,對各子域模型進行微調,這個過程重點考慮不同限界上下文內聚合的重新組合,同步需要考慮子域、限界上下文以及聚合之間的邊界、服務以及事件之間的依賴關係,確定最終的領域模型。

  • 微服務設計和拆分:根據領域模型的限界上下文和微服務的拆分原則,完成微服務的拆分和設計。

單體遺留系統的微服務設計¶

如果一個單體遺留系統,只是將面臨問題或效能瓶頸的模組拆分為微服務,而其餘功能仍為單體。我們只需要將這些特定功能領域理解為一個簡單的子領域,按照簡單領域建模方式進行領域模型的設計即可。但在新微服務設計中需要考慮新老系統之間的服務協議,必要時引入防腐層。

特別說明¶

雖然有些業務領域在事件風暴後發現無法建立領域模型,如資料處理或分析類場景,但本文所述的分層架構模型、服務之間規約和程式碼目錄結構在微服務設計和開發中仍然是通用的。

基於 DDD 的微服務設計和開發例項¶

為了更好的理解 DDD 的設計思想和過程,我們用一個場景簡單但基本涵蓋 DDD 設計思想的專案來說明微服務設計和開發過程。

專案基本資訊¶

專案主要目標是實現線上請假和考勤管理。基本功能包括:請假、考勤以及人員管理等。

  • 請假:請假人填寫請假單提交審批,根據請假人身份和請假天數進行校驗,根據審批規則逐級遞交審批,核批通過則完成審批。
  • 考勤:根據考勤規則,剔除請假資料後,對員工考勤資料進行校驗,輸出考勤統計表。
  • 人員管理:維護人員基本資訊和上下級關係。 ......

設計和實施步驟¶

步驟一:事件風暴¶

由於專案目標基本明確,我們在事件風暴過程中裁剪了產品願景,直接從使用者旅程和場景分析開始。

  • 1、場景分析:場景分析是一個發散的過程。根據不同角色的旅程和場景分析,儘可能全面的梳理從前端操作到後端業務邏輯發生的所有操作、命令、領域事件以及外部依賴關係等資訊(如下圖),如:請假人員會執行建立請假資訊操作命令,審批人員會執行審批操作,請假審批通過後會產生領域事件,通知郵件系統反饋請假人員結果,並將請假資料傳送到考勤以便核銷等。在記錄這些領域物件的同時,我們也會標記各物件在 DDD 中的層和物件型別等屬性,如:應用服務、領域服務、事件和命令等型別。

  • 2、領域建模:領域建模是一個收斂的過程。這個收斂過程分三步:第一步根據場景分析中的操作集合定義領域實體;第二步根據領域實體業務關聯性,定義聚合;第三步根據業務及語義邊界等因素,定義限界上下文。

    • 定義領域實體:在場景分析過程中梳理完操作、命令、領域事件以及外部依賴關係等領域物件後。分析這些操作應由什麼實體發起或產生,從而定義領域實體物件,並將這些操作與實體進行關聯。 在請假場景中,經分析需要有請假單實體物件,請假單實體有建立請假資訊以及修改請假資訊等操作。
    • 定義聚合:將業務緊密相關的實體進行組合形成聚合,同時確定聚合中的聚合根、值物件和實體。經分析專案最終形成三個聚合:人員管理、請假和考勤。在請假聚合中有請假單、審批軌跡、審批規則等實體,其中請假單是聚合根,審批軌跡是請假單的值物件,審批規則是輔助實體。
    • 聚合內須保證業務操作的事務性,高度內聚的實體物件可自包含完成本領域功能。聚合是可拆分為微服務的最小單元。在同一限界上下文內多個聚合可以組合為一個微服務。如有必要,也可以將某一個聚合獨立為微服務。
    • 定義限界上下文:根據領域及語義邊界等因素確定限界上下文,將同一個語義環境下的一個或者多個聚合放在一個限界上下文內。由於人員管理與請假聚合兩者業務關聯緊密,共同完成人員請假功能,兩者一起構成請假限界上下文,考勤聚合則單獨形成考勤限界上下文。
  • 3、微服務設計和拆分:理論上一個限界上下文可以設計為一個微服務,但還需要綜合考慮多種外部因素,如:職責單一性、效能差異、版本釋出頻率、團隊溝通效率和技術異構等要素。

由於本專案微服務設計受技術以及團隊等因素影響相對較小,主要考慮職責單一性,因此根據限界上下文直接拆分為請假和考勤兩個微服務。其中請假微服務包含人員和請假兩個聚合,考勤微服務只包含考勤聚合。

步驟二、領域物件及服務矩陣¶

將事件風暴中產出的領域物件按照各自所在的微服務進行分類,定義每個領域物件在微服務中的層、領域型別和依賴的領域物件等。

這個步驟最關鍵的工作是確定實體、方法、服務等領域物件在微服務分層架構中的位置以及各物件之間的依賴關係,形成服務矩陣(如下表)。這個過程也將在事件風暴資料的基礎上,進一步細化領域物件以及它們之間關係,並補充事件風暴中可能遺漏的細節。

確定完各領域物件的屬性後,按照程式碼模型設計各個領域物件在程式碼模型中的程式碼物件(包括程式碼物件所在的:包名、類名和方法名),建立領域物件與程式碼物件的一一對映關係。根據這種對映關係,相關人員可快速定位到業務邏輯所在的程式碼位置。

步驟三:領域模型及服務架構¶

根據領域模型中領域物件屬性以及服務矩陣,畫出領域物件及服務架構檢視(如下圖)。這個檢視可以作為標準的 DDD 分層領域服務架構檢視模型,應用在不同的領域模型中。這個模型可以清晰的體現微服務內實體、聚合之間的關係,各層服務之間的依賴關係以及應用層服務組合和編排的關係,微服務之間的服務呼叫以及事件驅動的前後處理邏輯關係。 在這個階段,前端的設計也可以同步進行,在這裡我們用到了微前端的設計理念,為請假和考勤微服務分別設計了請假和考勤微前端,基於微前端和微服務,形成從前端到後端的業務邏輯自包含元件。兩個微前端之上有一個整合主頁面,可根據頁面流動態載入請假和考勤的微前端頁面。

步驟四:程式碼模型設計¶

根據 DDD 的程式碼結構模型和各領域物件在所在的包、類和方法,定義出請假微服務的程式碼結構模型。應用層程式碼結構包括:應用服務以及事件釋出相關程式碼(如下圖)。

領域層程式碼結構包括一個或多個聚合的實體類以及領域服務相關程式碼(如下圖)。在本專案中請假微服務領域層包含了請假和人員兩個聚合。

領域模型中的一個聚合對應一個聚合程式碼包,如:人員和請假領域邏輯程式碼都放在各自的聚合程式碼包中,如隨著業務發展,人員管理功能需要從請假微服務中拆分出來,我們只需要將人員聚合程式碼包稍加改造並獨立部署即可快速釋出為人員管理微服務。

步驟五:詳細設計¶

在完成領域模型和程式碼模型設計後,我們就可以開始詳細設計了,詳細設計主要結合具體的業務功能來開展,主要工作包括:系統介面、資料庫表以及欄位、服務引數規約及功能等。

步驟六:程式碼開發¶

軟體開發人員只需要按照設計文件和功能要求,找到業務功能對應的程式碼位置,完成程式碼開發和服務編排即可。

步驟七:測試和釋出¶

完成程式碼開發後,由開發人員編寫單元測試用例,基於擋板模擬依賴物件完成跨服務的測試。單元測試完成後,在團隊內可進一步完成微服務與相應微前端的整合和測試,形成請假和考勤兩個業務元件。前端主頁面完成請假和考勤微前端頁面整合和頁面流及元件基礎資料配置,主頁面可以按照頁面流程動態載入請假和考勤微前端頁面。最終部署的軟體包包括:請假和考勤兩個微服務,請假和考勤兩個微前端,一個主頁面共計五個。這五個部署包獨立開發、獨立執行和獨立部署。

技術元件說明¶

主頁面和微前端採用:Vue(前端框架),ElementUI(UI 框架 -PC),VUX(UI 框架 - 移動端) 和 MPVUE(UI 框架 - 小程式) 等。微服務開發採用:Spring Cloud、Kafka、Redis 等。資料庫採用:PostgreSQL。

附錄一:DDD 名詞和術語¶

      • Event Storming(事件風暴):事件風暴是一項團隊活動,旨在通過領域事件識別出聚合根,進而劃分微服務的限界上下文。在活動中,團隊先通過頭腦風暴的形式羅列出領域中所有的領域事件,整合之後形成最終的領域事件集合,然後對於每一個事件,標註出導致該事件的命令(Command),再然後為每個事件標註出命令發起方的角色,命令可以是使用者發起,也可以是第三方系統呼叫或者是定時器觸發等。最後對事件進行分類整理出聚合根以及限界上下文。
      • Entity(實體):每個實體是唯一的,並且可以相當長的一段時間內持續地變化。我們可以對實體做多次修改,故一個實體物件可能和它先前的狀態大不相同。但是,由於它們擁有相同的身份標識,他們依然是同一個實體。例如一件商品在電商商品上下文中是一個實體,通過商品中臺唯一的商品 id 來標示這個實體。
      • ValueObject(值物件):值物件用於度量和描述事物,當你只關心某個物件的屬性時,該物件便可作為一個值物件。實體與值物件的區別在於唯一的身份標識和可變性。當一個物件用於描述一個事物,但是又沒有唯一標示,那麼它就是一個值物件。例如商品中的商品類別,類別就沒有一個唯一標識,通過圖書、服裝等這些值就能明確表示這個商品類別。
      • Aggregate(聚合):聚合是實體的升級,是由一組與生俱來就密切相關實體和值物件組合而成的,整個組合的最上層實體就是聚合。
      • Bounded Context(限界上下文):用來封裝通用語言和領域物件,為領域提供上下文語境,保證在領域之內的一些術語、業務相關物件等(通用語言)有一個確切的含義,沒有二義性。使團隊所有成員能夠明確地知道什麼必須保持一致,什麼必須獨立開發。