服務拆分與架構演進

領域驅動設計和服務自演進能力是內功。html

前言

《微服務的團隊應對之道》提到,微服務幫助企業提高其響應力,而企業須要從DevOps、服務構建、團隊和文化四點入手,應對微服務帶來的複雜度和各類挑戰,從而真正獲益。若是說運維能力是微服務的加油站,服務則是其核心。ios

gas

企業想要實施微服務架構,常常問到的第一個問題是,怎麼拆?如何從單體到服務化的結構?第二個問題是拆完後業務變了增長了怎麼辦?另外,咱們想要改變的系統每每已經成功上線,並有着活躍的用戶。那麼對其拆分還須要考慮現有的系統運行,如何以安全最快最低成本的方式拆分也是在這個過程當中須要回答的問題。數據庫

本文會針對以上問題,介紹咱們團隊在服務拆分和演進過程當中的實踐和經驗總結。後端

咱們項目架構的演化歷程

1-evolution-course

該項目始於2009年,到如今已有7年的時間。在這7年中覆蓋的業務線不斷擴大,從工單、差旅、計費、文件、報表、增值業務等;業務流程從部分節點到用戶端的全線延伸;7年間打造多個產品,架構經歷了屢次調整,從單體架構、RPC、服務化、規模化到微服務。安全

主要架構變遷以下圖所示:架構

2-process

在這7年架構演進路上,咱們遇到的主要挑戰以下:前後端分離

  • 如何拆?即如何正確理解業務,將單體結構拆分爲服務化架構?
  • 拆完後業務變了增長了怎麼辦?即在業務需求不斷髮展變化的前提下,如何持續快速地演進?
  • 如何安全地持續地拆?即如何在不影響當下系統運行狀態的前提下,持續安全地演進?
  • 如何保證拆對了?
  • 拆完了怎麼保證不被破壞?

問題1:如何將單體結構拆分爲服務化架構?

就如庖丁解牛同樣,拆分須要摸清內部的構造脈絡,在筋骨縫隙處下刀。那麼微服務架構中,咱們認爲服務是業務能力的表明,須要圍繞業務進行組織。拆分的關鍵在於正確理解業務,識別單體內部的業務領域及其邊界,並按邊界進行拆分。運維

1. 識別業務領域及邊界。

首先須要將客戶、體驗設計師、業務分析師、技術人員集結在一塊兒對業務需求進行溝通,隨後對其進行領域劃分,肯定限界上下文(Boundary Context),也稱戰略建模。模塊化

如下咱們常用的方法和參考的紅藍寶書:微服務

  • Inception-> User Journey | Scenarios,用於梳理業務流程,由粗粒度到細粒度逐一場景分析。
  • 四色建模,用於提取核心概念、關鍵數據項和業務約束。
  • 領域驅動設計-戰略設計,用於劃分領域及邊界、進行技術驗證。
  • Eventstorming,用於提取領域中的業務事件,便於正確建模。

3-modeling

Inception與DDD戰略設計的對比:

4-inception

一個業務領域或子域是一個企業中的業務範圍以及在其中進行的活動,核心子域指業務成功的主要促成因素,是企業的核心競爭力;通用子域不是核心,但被整個業務系統所使用;支撐子域不是核心,不被整個系統使用,該能力可從外部購買。一個業務領域和子域能夠包括多個業務能力,一個業務能力對應一個服務。領域的邊界即限界上下文,也是服務的邊界,它封裝了一系列的領域模型。

一個業務流程表明瞭企業的一個業務領域,業務流程所涉及的數據或角色或是通用子域,或是支撐子域,由其在企業的核心競爭力的角色所決定。好比企業有統一身份認證,決策不一樣部門負責不一樣的流程任務,那麼身份認證子域並不產生業務價值,不是業務成功的促成因素,可是全部流程的入口,於是爲通用子域,可爲單獨服務;而部門負責的業務則爲核心子域。

舉個例子

工單業務流程:

某企業爲服務人員提供工單服務的業務流程簡化以下。首先搜索服務人員,選取服務人員購買的服務,基於目標國家的工單流程,向服務人員收取資料,對其進行審計,最後發送結果。

5-work-order識別的領域:

其中服務爲其核心競爭能力,包括該企業對全球各國的政策理解,即法律流程,服務資料(問卷),計算服務,資料審計服務,相比其餘競爭對手的服務(價位/效率等),這些都爲改企業提供核心的業務價值,天然也是核心子域。而其餘用於統計改企業員工工做的工單,組織結構和員工爲支撐子域,並不直接產生業務價值

6-recognition

領域劃分的原則

在劃分的過程當中,常常糾結的一個問題是:這個模型(概念或數據)看起來放這個領域合適,放另外一個也合適,如何抉擇呢?

  • 第一,依據該模型與邊界內其餘模型或角色關係的緊密程度。好比,是否當該模型變化時,其餘模型也須要進行變化;該數據是否一般由當前上下文中的角色在當前活動範圍內使用。
  • 第二,服務邊界內的業務能力職責應單一,不是完成同一業務能力的模型不放在同一個上下文中。
  • 第三,劃分的子域和服務需知足正交原則。領域名字表明的天然語言上下文保持互相獨立。
  • 第四,讀寫分離的原則。例如報表需有單獨報表子域。核心子域的劃分更多基於來自業務價值的產生方,而非不產生價值的報表系統。
  • 第五,模型在不少業務操做中同時被修改和更新。
  • 第六,組織中業務部分的劃分也是一種參考,一個業務部門的存在每每有其獨特的業務價值。

簡單打個比方,同一個領域上下文中的模型要保持近親關係,五福之內,同一血統(業務)。

領域劃分的誤區和建議

  • 業務能力仍是計算能力?在劃分一些貌似通用的領域時,其實只是用到了通用的計算能力而不是業務能力,只需採用通用庫的方式進行封裝,而無需使用服務的方式。如咱們系統的模板服務,是構建通用的模板服務,服務於整個平臺的服務;仍是每一個服務擁有獨立的模板模塊?
  • 儘早識別剝離通用領域。如身份認證與鑑權領域,是企業系統中最複雜、有相對多變的領域,須要及早隔離它對核心業務的干擾。
  • 時刻促成技術人員與客戶、業務人員的對話。業務領域的劃分離不開對業務意圖的真正理解。而需求人員和體驗設計師對於User Journey的使用更熟悉,而技術人員、架構師對領域驅動設計、Eventstorming更熟悉。無論哪一種方法都要求跨角色的羣體協同工做,即客戶人員、業務分析師、體驗設計師與技術人員、架構師。而現實的狀況中,User Journey更多的在Inception,在需求階段進行,而領域驅動設計、Eventstorming更多的在開發設計階段被使用,故而需求階段常常缺失技術人員,而開發設計階段常常缺失客戶、業務人員的參與。 另外一個常見的現象是,Inception的參與人員和真正的開發團隊有可能不是同一個羣體,那麼Inception中的業務溝通每每以UI的方式做爲傳遞,所以在開發中常常只能經過UI設計來理解業務的真正意圖。 因此要想將正確的理解業務,作對軟件,須要時刻促成技術人員與客戶、業務人員的對話。

識別了被拆對象的結構和邊界,下一步須要決定拆分的策略和拆分的步驟。

2.拆分方法與策略

拆分方法須要根據遺留系統的狀態,一般分爲絞殺者與修繕者兩種模式。

  • 絞殺者模式 指在遺留系統外圍,將新功能用新的方式構建爲新的服務。隨着時間的推移,新的服務逐漸「絞殺」老的一流系統。對於那些老舊龐大難以更改的遺留系統,推薦採用絞殺者模式。
  • 修繕者模式 就如修房或修路同樣,將老舊待修繕的部分進行隔離,用新的方式對其進行單獨修復。修復的同時,需保證與其餘部分仍能協同功能。

咱們過去所作的拆分中多爲修繕者模式,其基本原理來自Martin Fowler的branch by abstraction的重構方法,以下圖所示:

7-branch-by-abstraction

就如咱們團隊所總結的16字重構箴言,我以爲十分的貼切:

「舊的不變,新的建立,一步切換,舊的再見」。

經過識別內部的被拆模塊,對其增長接口層,將舊的引用改成新接口調用;隨後將接口封裝爲API,並將對接口的引用改成本地API調用;最後將新服務部署爲新進程,調用改成真正的服務API調用。

同時,拆分建議從業務相對獨立、耦合度最小的地方開始。待團隊獲取相應經驗和基礎設施平臺構建完善後,再進行核心應用遷移和大規模的改造。另外,核心通用服務儘可能先行,如身份認證服務。

3. 拆分步驟

對於模塊的拆分包括兩部分:數據庫與業務代碼,能夠先數據庫後業務代碼,亦可先業務代碼後數據庫。然而咱們的項目拆分中遇到的最大挑戰是數據層的拆分。在2015年的拆分中發現,數據庫層因爲當時系統性能調優的驅動,在代碼中出現了跨模塊的數據庫連表查詢。這致使後期服務的拆分很是的困難。所以在拆分步驟上咱們更多的推薦數據庫先行。

4.數據庫拆分

咱們借鑑了重構數據庫一書中提到的方法,經過重複schema同步數據,對數據庫的讀寫操做分別進行遷移。以下圖所示:

8-TDDL

雖然技術上是可行的,然而這仍然佔用了大量沒必要要的時間,包括大量的數據遷移。這也是致使當時的拆分沒法在給定時間內完成的很大因素。

5. 咱們的結果:

系統架構圖:

9-system-architecture

問題2:拆分後業務變了增長了怎麼辦?

隨着客戶業務的變化,咱們的服務也在持續的增長,而其中碰到了一個特大的服務。服務的大小如何衡量呢?該服務生產代碼7萬行+,測試代碼14萬行+,測試運行時間2個小時。團隊中7個stream天天50%工做須要對這個服務進行更改,使得團隊間的依賴很是嚴重,獨立功能沒法單獨快速前行,交付速度及質量都受到了影響。

咱們的總結:

客戶的業務是在變化的,咱們對業務的認知也是逐漸的過程,因此Martin Fowler在他的文章中提出,系統的初期建議以單體結構開始,隨業務發展決定其是否被拆分或合併。那麼這也意味着這樣構建的服務在它的生命週期中必然會持續被拆分或合併。那麼爲了實現這樣一個目標,使系統擁有快速的響應力,也要求這樣的拆分必然是高效的低成本的。

所以,服務的設計須要知足以下的原則:

  • 服務要有明確的業務邊界,以單體開始並不意味着沒有邊界。 服務要有邊界,即便以單體開始也要定義單體時期的邊界。咱們系統中有一個名爲「Monkey」的服務,是在中國虎年啓動的,由此它並非一個業務概念。當這個服務的名字爲MonkeyAPI時,能夠想象5年來它變成了什麼?幾乎全部和這個產品相關的功能都放入了這個服務中。脫離平臺來看這一個產品的系統,其實它只是作了先後端分離而已。這個例子告訴咱們,沒有邊界就會致使大雜燴,以後對其進行整理和重造的代價很大,可能須要花費「幾代人」的努力。
  • 服務要有明確清晰的契約設計,即對外提供的業務能力。
  • 服務內部要保持高度模塊化,纔可以容易的被拆分。
  • 可測試。

問題3:如何安全地持續地拆?

就如前言中提到的,系統已經上線大量的用戶正在使用,如何在不影響當下系統運行狀態的前提下,持續安全地演進?其實持續演進就是一場架構層次的重構,在這樣的路上一樣須要:

  • 壞味道驅動,架構的壞味道是代碼壞味道在更高層次的展示,也就意味着架構的混亂程度一樣反映了該系統代碼層的質量問題。
  • 安全小步的重構。
  • 有足夠的測試進行保護——契約測試。
  • 持續驗證演進的方向。

真正有挑戰的問題4:如何保證拆對了?

拆分不能沒有目標,尤爲在具備風險的架構層次拆分更需謹慎。那麼咱們如何驗證拆分的結果和收益?或許它能夠提升開發效率,交付速度快,上線快,宕機時間也短,還能提升開發質量,可擴展性好,穩定,維護成本低,新人成長快,團隊容易掌握等等。然而軟件開發是一個複雜的事情,拆分能夠引發多個維度的變化,度量的難度在於如何準肯定位由拆分這一單一因素引發的價值的變化(增長或下降)。

其實要回答這個問題,仍是要回到拆分之初:爲何而拆? 我所見過的案例中有由於政治緣由拆的、業務發展須要的、系統集成驅動的等等;有因之而成功的,也有因之而失敗的。拆並非一件容易的事,有諸多的因素。我認爲無論表象是什麼,拆以前須要弄清拆分的價值所在,這也是咱們能夠保證拆分結果的源頭。

總結

系統可由單體結構開始,不斷的演進。而團隊須要對業務保持敏感,與客戶、業務人員進行業務對話,不斷修煉領域驅動設計和重構的能力。

team

在拆分的路上,咱們的經驗顯示其最大的障礙來自意大利麪同樣的系統。無論咱們是什麼樣的架構風格,高內聚低耦合的模塊化代碼內部質量仍然是咱們架構演進的基石。具備夯實領域驅動設計和重構功底的團隊才能夠應對這些挑戰,持續演進,保持其生命力。而架構變遷以前須要弄清背後的變遷動因與價值,探索性前進,及時反饋驗證,纔是正解。那麼咱們如何保證架構不被破壞呢?這個問題會在後續的文章中持續探討。

最後,勿忘初心,且行且演進。

相關文章
相關標籤/搜索