微服務分層設計和領域建模

爲什麼要做分層設計和領域建模

1、提高開發效率
2、提高應用的可擴展性及可維護性

微服務設計應對之道

推薦將Web服務架構分爲五層:基礎設施層、領域服務層、應用服務層、網關層和用戶界面層(表示層)。領域服務層和應用服務層均可以採用微服務設計進行拆分,其中領域服務層推薦按照DDD領域建模進行領域劃分,設計爲一個個領域模塊微服務,每個微服務高度內聚,僅關注自己的業務,領域服務間通過接口調用進行松耦合。這種設計方案可以大大簡化大型複雜系統,並且在後期的維護中優勢會日漸凸顯,然而把大型複雜系統分而治之拆成微服務的同時也對架構師和開發人員提出了更高的要求。
在這裏插入圖片描述

爲什麼會有網關層?

互聯網公司產品的輸出形式無外乎Web應用(網站、或者網絡服務),並且爲了更好的適配PC站和App,一般會採用前後端分離的應用設計方案,前後端分離在於應用層提供API接口,表示層調用API實現數據交互,這時候會產生一個需求——內部網絡應用系統如何把自己的服務輸出到互聯網上,供外部系統或者瀏覽器網頁訪問。最直接的方式就是把應用層直接暴露在公網上,但我們不建議這麼做,應用層服務更多的是關注業務應用,對網絡級的系統安全性(防DDOS、釣魚、跨域等)、請求監控等缺乏考慮,這些工作交給網關層統一管理會輕鬆很多(比如淘寶的TOP平臺)。

這時候在Web應用系統中引入網關層用於銜接表示層和應用層,這樣可以更好的劃分各層的職能。網關層也可以看作是應用服務層的對外包裝層。如果一定要把網關層做到應用服務層裏理論上也是可行的,比如針對於Spring Cloud這種框架下的微服務體系,可以考慮直接暴露應用層,只需輔助一些運維手段即可。

各層的職能是什麼?

用戶界面層/表示層

負責向用戶顯示和解釋用戶指令,這裏指的用戶可以是另一個計算機系統,不一定是使用用戶界面的人(比如外部應用調用對應接口)。

網關層

負責提供對外的HTTP服務或者其他應用層協議(這裏是指OSI七層協議中的應用層)服務。

應用服務層

業務通用能力的處理層,定義軟件要完成的任務,並且指揮表達領域概念的對象來解決問題。具體包括:

統一權限校驗

如上文所說,網關層只負責網絡級的安全防護,業務層的權限校驗則需要應用層來完成,試想一個沒有應用層的微服務體系,就意味着每一個微服務都需要加上權限校驗邏輯,這不僅編碼上困難(可以用過濾器,AOP),而且對於成千上萬個微服務(據瞭解,騰訊目前微服務數量已經超過2萬,大衆點評有將近千個微服務)來說,這會浪費大量時間,調用鏈越長,浪費的時間越多。換句話說,微服務體系有一個不突出但是很重要的特徵,領域間環境安全,領域間的通信應當是可信的,否則分佈式的缺點(多服務意味着多次通信)會被加劇。

業務數據網關

舉個例子,一個order-service提供了一個queryOrder的接口,輸入起始日期查詢對應的訂單列表,其有2個消費者:C端網站應用服務和報表應用服務,C端網站應用服務只需要知道訂單的基本信息如下單時間、商品名稱、金額就可以了,而報表應用服務是給管理者看的,需要的訂單數據很全,除了C端網站應用服務需要的之外,還需要看平臺與商家的結算金額。根據最小權限原則,以及領域層服務設計應當是調用者無關的,我們肯定不能爲調用方寫定製接口(寫不完的,有的要這個數據,有的要那個數據,每次新增調用方,領域服務還得找人修改)。而如果我們統一使用的全量數據,並且沒有應用層(同樣的也沒有應用層模型DTO了),那麼很可能我們吐出去的數據包含了我們與商家的結算價,這會引發很多不必要的麻煩的。所以應用層還充當了業務數據網關的作用,應用層應用服務需要保證僅吐出調用方感興趣的數據。

資源控制和緩存

想象一下雙十一高併發的情況,如果查詢庫存每次都查庫是多麼恐怖的一件事。所以一般僅在支付的時候做一次庫存校驗,而在商品展示時查緩存的庫存即可。那麼問題來了,如果沒有應用層,緩存直接放在庫存微服務上是否可行呢?首先這會入侵庫存領域,庫存微服務需要按照調用方的需求做特定時間的緩存,而不是自己想緩存多久就多久,我想庫存微服務的開發者也會很不滿的,他會提出,讓你自己去做緩存。他的方案是科學的,因爲還有一些其他服務可能需要實時的數據。這時候就需要有一層來做對其下方微服務返回的數據按照應用自身的需求進行必要的緩存,而不是把這些需求都推給資源提供方,想象一下一個資源提供方有多少需求者,每個需求方都有自己的定製需求,該多痛苦。當然這一點也不是說微服務自身不能做緩存,微服務自身的緩存一定是考慮自身域的合理性後的一個措施(比如訂單查詢服務會做一個500ms的緩存,因爲不會有正常人500ms裏點兩次查詢還必須要求兩次都是最新的),而不是由調用方來決定的。

資源聚合和加工(包括定時任務、數據轉換)

應用層應用根據自身需求來對下層返回的數據進行聚合和處理。例如:任何APP都有首頁,而首頁的數據可能是五花八門的,可以有用戶暱稱、最近下的訂單簡要信息、最近支出曲線、積分信息等。這4個信息可以來自4個領域微服務,他們是:用戶中心、訂單中心、支付中心和積分中心。那麼有讀者會說,直接暴露微服務讓前端分別調用4個接口再做聚合不是也行嗎?顯然這種粗暴的方式是極其不合理的,會額外增加廣域網網絡調用3次不說,還傳輸了很多不必要的信息。

其他(如參數校驗、異常處理等)

應用服務層這一層所負責的工作對業務來說意義重大,也是與其他系統的應用層進行交互的必要渠道。應用層要儘量簡單,不包含業務規則或者知識,而只爲下一層中的領域對象協調任務,分配工作,使他們互相協作。它沒有反應業務情況的狀態,但是卻可以具有另外一種狀態,爲用戶或者程序顯示某個任務的進度。

領域服務層

負責表達業務概念,業務狀態信息以及業務規則。儘管保存業務狀態的技術細節是由基礎設施層實現的,但是反應業務情況的狀態是由本層控制並且使用的。領域服務層是業務軟件的核心。

基礎設施層

爲上面各層提供通用的技術能力,爲應用層傳遞消息,爲領域層提供持久化機制等等。互聯網Web應用系統中基礎設施包含了數據持久化服務,中間件服務(數據庫,Redis,Memcached,ES等等)以及第三方服務等。

各層的設計原則有哪些?

高內聚(複用)低耦合(解耦)

  • 緊密關聯的事物應該放在一起,每個服務是針對一個單一職責的業務能力的封裝,專注做好一件事情(每次只有一個更改它的理由)。如下圖:有四個服務a,b,c,d,但是每個服務職責不單一,a可能在做b的事情,b又在做c的事情,c又同時在做a的事情,通過重新調整,將相關的事物放在一起後,可以減少不必要的服務。

  • 輕量級的通信方式

    • 同步RESTful(GET/PUT/POST…),基於http,讓服務間的通信變得標準化並且無狀態;後端微服務之間通信可以使用RPC
    • 異步(消息隊列/發佈訂閱)
  • 避免服務之間相互依賴

  • 避免在服務與服務之間共享數據庫
    在這裏插入圖片描述

層級調用

1、每一層只能依賴於它下方的層,除領域服務層外,其它層均不建議出現同層內微服務間調用的情況,此外,應儘量避免循環調用或互相調用的產生;
2、禁止跨層級調用;
3、下層向上層發起的通信只能通過中間件等間接方式進行;
4、上層和下層只能有鬆散耦合(各自爲獨立個體,通過簡單引用關聯)。在某些微服務框架比如kratos中,可以把api包提供給上層引用即可。而Spring Cloud的上下層耦合更爲鬆散,通過契約約定即可。前者的優點是調用者可以直接使用提供方定義好的契約和方法。後者的優點則在於最大限度的降低了耦合,避免在上層無限制的進行下層包引入。

舉例說明應用服務層和領域服務層之間的關係,有一家上市企業A公司,靠賣水果發家,其首席架構師科學合理的按照DDD搭建了一套基於微服務體系的賣水果應用,其架構圖如下::
在這裏插入圖片描述
今年水果行情一般,而房地產十分火熱,A公司高層發現房地產帶動的五金行業也十分火熱,於是下達任務給技術部,要求其立即着手搭建五金銷售系統,貨源已經談好。得益於首席架構師之前優秀的架構設計,他發現只需要做一個賣五金的網站以及另外對微服務進行微量的調整即可滿足老闆的需求——因爲賣五金和賣水果並無本質區別,他們涉及的環節幾乎一致。加入五金售賣的系統架構圖如下:
在這裏插入圖片描述
可見應用服務層代表是某一個業務應用,它代表的更多的是從需求出發的應用定義,而領域服務層則是業務領域按照自身的邊界進行設計的一個高內聚的服務體。應用層通過協調和組合各個領域服務即可形成一個新的應用服務。《領域驅動設計》中明確指出,在設計領域服務時無需考慮表示層和持久層服務的東西。

領域服務層微服務如何拆分?

根據DDD理論,領域建模主要發生在領域服務層,各領域模塊都應該是高內聚低耦合的,具有清晰的業務邊界。如何切分領域模塊並沒有一個明確的規則,不同的場景下可能相同的業務塊邊界也不盡相同。這裏提幾點領域劃分的個人建議:

  • 領域設計一定要有清晰的功能邊界。一個領域服務對應了一個功能集合,這些功能一定是有一些共性的。比如,訂單服務,那麼創建訂單、修改訂單、查詢訂單列表,一般是訂單域的功能集合。
  • 一般按垂直業務拆分,一個服務對應獨立的庫(也可能出現多個服務公用一個庫,從設計上通常沒有公用表),絕大多數情況下,一個服務和一個數據庫搭配,對外提供接口。
  • 領域拆分並不是一步到位的,應當根據實際情況逐步展開。從單體應用到微服務體系的拆分過程能很好的說明這個問題。所以如果一開始不知道應該劃分多細,完全可以先粗粒度劃分,然後隨着需要,初步拆分。比如一個電商一開始索性可以拆分爲商品服務和交易服務,一個負責展示商品,一個負責購買支付。隨後隨着交易服務越來越複雜,就可以逐步的拆分成訂單服務和支付服務。
  • 領域拆分並不是一成不變的,應當具體情況具體分析。比如大衆點評,其訂單服務就拆分爲了order-service和order-query-service,一來爲了讀寫分離,二來order-query-service作爲單獨應用可以按需水平擴容。
  • 領域可以是多個子領域的一個虛擬集合,換句話說多個微服務也可以形成一個大域,不必糾結於領域和微服務之間的數量對應關係。例如在做架構設計的時候可能就把訂單域作爲一個領域,代表了這個域就是關於訂單的,具體該有幾個微服務,這需要更細的詳細設計來提供。
  • 領域層服務設計應當是調用者無關的。這一點有點像第一點,但是它強調的是領域層服務的設計不應該受調用者的影響,這個觀點在《領域驅動設計:軟件核心複雜性應對之道》這本書裏也可以找得到[4]。領域層服務開發和設計的理念是關注自己的域,一旦邊界劃分清楚了,開發所需要考慮的永遠都只是輸入和輸出,提供的服務一定是儘可能通用的,面向功能來開發的,而不是面向調用方來開發的。比如某個調用方提出了一個需求:調用方B希望A服務提供一個買汽車的接口,那麼A服務設計的接口就應該是buyCar(),而不是buyCarForA()。

Q&A

是否每一層都可以直接訪問數據庫?

領域服務層承載了數據存儲和訪問的能力,它與基礎設施曾進行數據交換,包括 MySQL、Oracle、Redis、MongoDB、ElasticSearch、PostgreSQL、HBase 等。在應用服務層調用時,它對底層的數據實現方式是無感知的,無論是哪種數據存儲方式,以及它是遠程數據,還是本地數據,都可以非常容易的調用。換句話說,我們需要將數據的查詢和更改操作限制在領域服務層,並只能被應用服務層訪問。

領域服務層多個微服務之間是否可以共享數據庫?

在這裏插入圖片描述
嚴格禁止多個微服務之間共享數據庫,理由如下:

  1. 強耦合:爲多個服務提供單個數據庫的傳統設計造成了緊密耦合,如果有多個服務訪問同一個數據庫,那麼任何模式更改都需要在所有服務之間進行協調,這在現實世界中可能會導致部署更改的額外工作和延遲。
  2. 擴展性差:使用這種設計很難擴展單個服務,因爲您只能選擇擴展整個單塊數據庫。
  3. 獨立部署困難
  4. 性能問題:提高應用程序性能成爲一個挑戰,使用一個共享數據庫,在一段時間內,您最終會得到一個巨大的表。

怎麼破?

需要重點考慮對立數據庫的拆分,即需要想出一個可靠的策略,將數據庫分割爲多個與領域服務層微服務對齊的小型數據庫。簡而言之,您需要將您的領域服務層微服務從使用單一的共享數據庫中拆分出來,您應該以這樣一種方式設計您的微服務體系結構,即每個單獨的領域服務層微服務都有自己的獨立數據庫和自己的領域數據。這將允許您獨立部署和擴展微服務。
在這裏插入圖片描述
在這裏插入圖片描述
領域服務層微服務應該遵循領域驅動設計並具有有限的上下文。您需要基於領域來設計微服務,領域與微服務的功能是一致的。這就像遵循代碼優先方法而不是數據優先方法一樣——因此您首先設計模型。這是一種與傳統的在開始處理新需求或新項目時首先設計數據庫表的方法完全不同的方法。您應該始終努力保持業務模型的完整性。在設計數據庫時,查看應用程序功能並確定它是否需要關係模式。
在這裏插入圖片描述
數據庫應該被視爲每個領域服務層微服務的私有數據庫。沒有其他微服務可以直接修改存儲在另一個微服務中的數據庫中的數據。在上圖中,訂單服務不能直接更新定價數據庫,只能通過微服務API訪問。這有助於您實現不同服務之間的一致性。
在這裏插入圖片描述
隊列的消息可以被視爲事件,並且可以遵循發佈-子模型。發佈者發佈消息,而不知道已經訂閱了事件流的使用者。體系結構中組件之間的鬆散耦合可以構建高度可伸縮的分佈式系統。

其它

配置類的用配置中心

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