這篇文章講的太好了,生怕之後被刪掉。轉到我博文列表裏把-。-前端
原文地址
ios
正文:數據庫
開發者在剛開始嘗試實現本身的微服務架構時每每會產生一系列問題 :架構
回答上面的問題須要首先了解微服務設計的邏輯,科學的架構設計應該經過一些輸入並逐步推導出結果,架構師要避免憑空設計和「拍腦門」的作法。app
解耦的單體應用和微服務系統在邏輯上是同樣的。對於服務拆分的邏輯來講,先設計高內聚低耦合的領域模型,再實現相應的分佈式系統是一種比較合適的方式。框架
服務的劃分有一些基本的方法和原則,經過這些方法能讓微服務劃分更有操做性。最終在微服務落地實施時也能按圖索驥,不管是對遺留系統改造仍是全新系統的架構都能遊刃有餘。運維
在開始劃分微服務以前,架構師須要在大腦中有一個重要的認識:微服務只是手段,不是目的。dom
微服務架構是爲了讓系統變得更容易拓展、更富有彈性。在把單體應用變成靠譜的微服務架構以前,單體系統的各個模塊應該是合理、清晰地。異步
也就是說,從邏輯上單體系統和微服務沒有區別,某種理想狀況下微服務只是把單體系統的各個模塊分開部署了而已(最近流行的monorepo把多個服務的代碼倉庫以模塊的形式組織到了一塊兒,證實了這一點)。分佈式
大量的實踐教訓告訴咱們,混沌的微服務架構,比解耦良好的單體應用會帶來更多麻煩。
(混亂的微服務VS良好的單體)
開源社區爲此進行了大量討論,試圖對系統解耦尋找一種行之有效的方法,所以具備十幾年歷史的領域驅動設計(DDD)方法論被從新認識。領域驅動設計立足於面向對象思想,從業務出發,經過領域模型的方式反映系統的抽象,從而獲得合理的服務劃分。
採用 DDD 來進行業務建模和服務拆分時,能夠參考下面幾個階段:
從 DDD 的限界上下文往微服務轉化,並獲得系統架構、API列表、集成方式等產出。
(使用DDD劃分微服務的過程)
抽象須要找到看似無關事務的內在聯繫,對微服務的設計尤其重要。
假設有一天,你在某電商網站購買了一臺空調,當你支付了空調訂單的費用後,又讓你再次支付安裝訂單費用,你確定大爲光火。緣由僅僅多是架構師在設計系統時,爲空調這種普通產品生產了一個訂單,而安裝做爲了另外業務邏輯生成了單獨的訂單。
你必定以爲這個例子太傻了,架構師不會這點都沒考慮到,」安裝「 應該被抽象成一個產品,而」安裝行爲「能夠做爲另一個服務實現。然而現實的例子比比皆是,電信或移動營業廳還須要用戶分兩步辦理號卡業務、寬帶業務。原始是不合適的抽象模型形成的,並最終影響了微服務的劃分。
咱們可使用概念圖來描述一些概念的抽象關係。
(商品這一律唸的概念圖)
若是沒有抽象出領域模型,就得不到正確的微服務劃分。
經過利用DDD對系統從業務的角度分析,對系統進行抽象後,獲得內聚更高的業務模型集合,在DDD中一組概念接近、高度內聚並能找到清晰的邊界的業務模型被稱做限界上下文(Bounded Context)。
限界上下文能夠視爲邏輯上的微服務,或者單體應用中的一個組件。在電商領域就是訂單、商品以及支付等幾個在電商領域最爲常見的概念;在社交領域就是用戶、羣組、消息等。
DDD的方法論中是如何找到子系統的邊界的呢?
其中一項實踐叫作事件風暴工做坊,工做坊要求業務需求提出者和技術實施者協做完成領域建模。
把系統狀態作出改變的事件做爲關鍵點,從系統事件的角度觸發,提取能反應系統運做的業務模型。再進一步識別模型之間的關係,劃分出限界上下文,能夠看作邏輯上的微服務。
事件是系統數據流中的關鍵點,相似於電影製做中的關鍵幀。在未創建模型以前,系統就像是一個黑盒,不斷的刺探系統的狀態的變化就能夠識別出某種反應系統變化的實體。
例如系統管理員能夠登陸、建立商品、上架商品,對應的系統狀態的改變是用戶已登陸、商品已建立、商品已經上架;相應的顧客能夠登陸、建立訂單、支付,對應的系統狀態改變是用戶已登陸、訂單已建立、訂單已支付。
因而能夠經過收集上面的事件瞭解到,「哦,原來是商品相關事件是對系統中商品狀態作出的改變,商品能夠表達系統中某一部分,商品能夠做爲模型」。
(利用事件刺探業務黑盒並抽象出模型)
在獲得模型以後,經過分析模型之間的關係得出限界上下文。例如商品屬性和商品相對於用戶、用戶組關係更爲密切,經過這些關係做出限界上下文拆分的基本線索。
其次是識別模型中的二義性,讓限界上下文劃分更爲準確。
例如,在電商領域,另一個不恰當設計的例子是:把訂單中的訂單項當作和商品一樣的概念劃分到了商品服務,但訂單中的商品實際上和商品庫中的商品不是同一個概念。
當訂單須要修改訂單下的商品信息時,須要訪問商品服務,這勢必形成了訂單和商品服務的耦合。
合理的設計應該是:商品服務提供商品的信息給訂單服務,可是訂單服務沒有理由修改商品信息,而是訪問做爲商品快照的訂單項。
訂單項應該做爲一個獨立的概念被劃分到訂單服務中,而不是和商品使用同一個概念,甚至共享同一張數據庫表。
(典型具備」二義性「陷阱的場景)
」訂單下的商品「和」商品「在不一樣的系統中實際上表達不一樣的含義,這就是術語」上下文「的由來。一組關係密切的模型造成了上下文(context),二義性的識別能幫咱們找到上下文的邊界(bounded)。一樣的例子還有 「訂單地址」 和 「用戶地址」的區別。
固然,在DDD中具體識別限界上下文的線索還不少,例如模型的生命週期等,咱們會在後面的文章中逐步展開。在後續的文章中,咱們會介紹更多關於 DDD 和事件風暴的思想和原理。
前面咱們說到限界上下文能夠做爲邏輯上的微服務,但並不意味着咱們能夠直接把限界上下文變成微服務。在這以前很重要的一件事情是對模型進行驗證,若是咱們獲得的限界上下文被抽象的不良好,在微服務實施後並不能獲得良好的拓展性和重用。
限界上下文被設計出來後,驗證它的方法能夠從咱們採用微服務的兩個目的出發:下降耦合、容易擴展,能夠做爲限界上下文評審原則:
原則1,設計出來的限界上下文之間的互相依賴應該越少越好,依賴的上游不該該知道下游的信息。(被依賴者,例如訂單依賴商品,商品不須要知道訂單的信息)。
原則2,使用潛在業務進行適配,若是能在必定程度上響應業務變化,則證實用它指導出來的微服務能夠在至關一段時間內足以支撐應用開發。
(通常抽象程度的領域模型)
上圖是一個電信運營商的領域模型的局部,這部分展現了電信號碼資源以及羣組、用戶、寬帶業務、電話業務這幾個限界上下文。
主要業務邏輯是,系統提供了號碼資源,用戶在建立時會和號碼資源進行綁定寫卡操做,最後再開通電話或寬帶業務。在開通電話這個業務流程中,號碼資源並不須要知道調用者的信息。
可是理想的領域模型每每抽象程度、成本、複用性這幾個因素中獲取平衡,軟件設計每每沒有理想的領域模型,大多數狀況下都是平衡各類因素的苟且,所以評審領域模型時也要考慮現實的制約。
(」抽象」的成本)
用一個簡單的圖來表達話,咱們的領域模型設計每每在複用性和成本取得平衡的中間區域纔有實用價值。
前面電信業務一樣的場景,業務專家和架構師表示,咱們須要更爲高度的抽象來知足將來更多業務的接入,所以對於兩個業務來講,咱們須要進一步抽象出產品和訂單的概念。
可是同時須要注意到,咱們最終落地時的微服務會變得更多,也變得更爲複雜,固然優點也是很明顯的 —— 更多的業務能夠接入訂單服務,同時訂單服務不須要知道接入的具體業務。
對於用戶的感知來講,能夠一次辦理多個業務並統一支付了,這正是某電信當前的痛點之一。
(高度抽象的領域模型)
在大量使用DDD指導微服務拆分的實踐後,咱們發現不少系統設計存在一些常見的誤區,主要分爲三類:未成功作出抽象、抽象程度太高、錯誤的抽象。
在實際開發過程當中,你們都有一個體會,設計階段只考慮了一些常見的服務,可是發現項目中有大量能夠重用的邏輯,並應該作成單獨服務。當咱們在作服務拆分時,遺漏了服務的結果是有一些業務邏輯被分散到各個服務中,並不斷重複。
如下是一個檢查單,幫助你檢查項目上常見的抽象是否具有:
對微服務或DDD理解不夠。模型具備二義性,被放到不一樣的限界上下文。例如,訂單中的收貨地址、用戶配置的經常使用地址以及地址庫中的標準地址。
這三種地址雖然名稱相似,可是在概念上徹底不是一回事,假如架構師將」地址「劃分到了標準地址庫中,勢必會形成用戶上下文和系統配置上下文、訂單上下文存在沒必要要的耦合。
(左邊爲抽象錯誤帶來的依賴,右邊爲正確的依賴關係)
上圖的右邊爲正常的依賴關係,左邊產生了不正常的依賴,會進一步產生雙向依賴。
在系統設計時,領域模型的二義性是一個比較難以識別和理解問題。好在咱們能夠經過畫概念圖來梳理這些概念的關係,概念圖是中學教輔解釋大量概念的慣用手段,在表達系統設計時同樣有用。
(電商系統中「地址」概念的梳理)
與地址相似的常見還有商品和訂單項中的商品;用戶和用戶組之間有一個成員的概念;短信的概念應該更爲具體到一條具體的短信和短信模板的區別。
組織對架構的干預
另一種使人感到驚訝的架構問題是企業的組織架構和團隊劃分影響了領域模型的正確創建。
有一些公司按照渠道來劃分了團隊,甚至按照 To C (面向於用戶)和 To B(面向企業內部)劃分的團隊,最終設計出來的限界上下文中赫然出現 」C端文章服務「,」B端文章服務「。
不乏有一些公司由於團隊職責的關係,將本應該集中的服務不得已下放給應用或者BFF(面向前端的backend)。對於這類問題,其實超出了DDD能解決的範圍,只能說在建模時警戒此類行爲對系統形成很嚴重的影響。
另外企業組織架構和技術架構的關係,請參考康威定律的敘述。一個由無數敏捷團隊組成的企業,和微服務有自然的聯繫;傳統實時瀑布模型的企業,在大型軟件時代競爭力十足,可是在互聯網時代卻無力應對變化。
(常見一些公司的組織架構)
抽象程度太高最典型的一個特徵是獲得的限界上下文極端的微小。回到咱們成本、複用性和抽象程度這幾個概念上來,上面咱們討論過,抽象程度雖然能夠帶來複用性的提升,可是帶來的成本很是高,甚至不可接受。
抽象程度太高帶來的成本有:更多的微服務部署帶來的運維壓力、開發調試難度提升、服務間通訊帶來的性能開銷、跨服務的分佈式事務協調等。所以抽象不是越高越好,應根據實際業務須要和成本考慮。
那相應的,微服務到底應該多小呢?
業界流傳一句話來形容,微服務應該多小:「一個微服務應該能夠在二週內完成重寫「。這句話可能只是一句調侃,若是真的做爲微服務應該多微的標準是不可取的。
微服務的大小應該取決於劃分限界上下文時各個限界上下文內聚程度。
訂單服務每每是不少IT系統中最爲複雜、內聚程度最高的服務,每每比較龐大,但沒法強行分爲 」訂單part1「 」訂單part2「 等多個微服務;
一樣,短信服務可能僅僅負責和外部系統對接,表現的極爲簡單,但咱們每每也須要單獨部署。
在經過 DDD 獲得領域模型和限界上下文後,理論上咱們已經獲得了微服務的拆分。可是,限界上下文到系統架構還須要完成下面幾件事。
一個合理的分佈式系統,系統之間的依賴應該是很是清晰地依賴,
在軟件開發中指的是一個應用或者組件須要另一個組件提供必要的功能才能正常工做。所以被依賴的組件是不知道依賴它的應用的,換句話說,被調用者不須要知道調用方的信息,不然這不是一個合理的依賴。
在微服務設計時,若是 domain service 須要經過一個 from 參數,根據不一樣的渠道作出不一樣的行爲,這對系統的拓展是致命的。例如,用戶服務對於訪問他的來源不該該知曉;用戶服務應該對訂單、商品、物流等訪問者提供無差異的服務。
所以,微服務的依賴關係能夠總結爲:上游系統不須要知道下游系統信息,不然請從新審視系統架構。
拆分微服務是爲了更好的集成到一塊兒,對於後續落地來講,還有服務集成這一重要的階段。微服務之間的集成方式會受到不少因素的制約,前面在討論微服務到底有多微的時候就順便提到了集成會帶來成本,處於不一樣的目的能夠採用不一樣的集成方式。
這三種集成方式耦合程度由高到低,適用於不一樣的場景,須要根據實際狀況選擇,甚至在系統中可能同時存在。服務間集成的方式還有其餘方式,通常來講,上面三種微服務集成的方式能夠歸納目前常見系統大部分需求。
第一次讀DDD相關的資料和書籍時,沒有記住DDD的不少概念,可是子域劃分像極了潮汕牛肉火鍋的劃分圖,給我留下深入的印象。DDD 強調技術人員和業務人員共同協做,DDD 對圖的繪製表現的很是隨意天然。
可是在作系統設計時,應該使用更爲準確和容易傳遞的架構圖,例如使用 C4 模型中的系統全景圖(System Landscape diagram)來表達微服務之間的關係。固然你也可使用UML來完成架構設計。C4 只是層次化(架構縮放)方式表達架構設計,和UML並不衝突。
系統架構圖除了微服務的關係以外,也須要講技術選型表達出來。
微服務集成方式除了經過架構圖標識以外,最好也經過API列表的方式將事件風暴中的事件轉換爲API;除此以外,能夠將DDD領域模型細化成聚合根、實體、值對象,請參考DDD的戰術設計。
邏輯每每比經驗更爲重要。寫這篇文章的初衷是爲了回答一個問題:若是老闆問我,你這個微服務劃分的依據是什麼,我該怎麼有說服力的回覆?
我該回答 「具體狀況具體分析?By experience?」仍是說,我是經過一套方法對業務邏輯進行分析獲得的。當沒有足夠的經驗直接解決問題,或問題龐大到不足以使用經驗解決時,能支撐你作出決策就只有對輸入問題進行有效的分析。
使用 DDD 指導微服務劃分,能在必定程度上彌補經驗的不足,作出有理有據的系統架構設計。