一文搞懂SaaS應用架構:應用服務、應用結構、應用互動設計

架构师汤师爷發表於2024-10-21

大家好,我是湯師爺~

今天系統性地聊聊SaaS應用架構設計。

應用架構概述

我們已經完成了SaaS系統的定位分析,明確了系統的目標和核心能力。這為接下來的應用架構設計奠定了基礎。

應用架構就像整個SaaS系統的骨架,決定了系統的整體結構和各個元件之間的關係。接下來,我們會深入探討應用架構的三個核心要素:應用服務、應用結構和應用互動。這些要素共同構成了一個體系化的SaaS系統架構。如圖所示。

通常,應用架構設計包括以下幾個步驟:

  • 識別應用服務:根據業務架構,把業務需求轉化為IT系統,找出關鍵的應用服務。
  • 劃分應用結構:設計應用結構,以及與業務流程、資料之間的關係,明確各部分的職責。
  • 設計應用互動:規劃各個應用結構之間如何互動和整合,確保系統各部分協調運作。

應用服務設計

在設計應用服務之前,我們先搞清楚什麼是應用服務,以及重要性。

應用服務的概念

應用服務是對一個或一組密切相關的業務物件及其操作的封裝。

應用服務要明確定義自己的責任範圍,將相關業務功能和物件組合在一起,避免暴露內部細節。

它需要整合因為同一原因變化的功能和資料,同時分離那些因為不同原因變化的部分。這樣的設計方法,確保了服務的內聚性和靈活性。

應用服務的概念源自SOA(面向服務的架構)和微服務架構的興起。透過把系統功能拆分成多個獨立的服務,可以提高系統的可維護性、可擴充套件性和靈活性。

應用服務如何劃分?

應用服務在應用架構中非常重要,它把系統的核心功能“打包”起來,提供給外部的業務流程使用,可以看作是SaaS系統對外的“門面”。使用者或者其他系統透過呼叫應用服務來實現特定的業務功能。那麼,怎麼設計應用服務呢?

1、對齊業務能力,劃分粒度適中的應用服務,職責單一。

在劃分應用服務粒度時,可以參考領域驅動設計(DDD)中的"限界上下文"概念。業務物件類似於限界上下文中的聚合根,是應用服務的核心。

通常情況下,我們會基於業務能力來劃分應用服務,每個業務能力都對應一到多個獨立的應用服務,每個應用服務用於支撐特定的業務能力。如圖所示。

將應用服務與業務能力對齊,確保系統功能緊密貼合業務需求,避免技術實現與業務邏輯脫節。

如果一個應用服務支撐了過多的業務能力,需要評估其內部是否關聯了過多的業務物件。在這種情況下,可以考慮將多個業務物件進行分組,從而將該應用服務拆分為多個更小、更專注的服務。

2、圍繞業務物件,提供具體的業務功能,避免包含不相關的功能。

從外部來看,應用服務通常有明確的業務含義,主要圍繞一個或一組密切相關的業務物件進行操作。

圍繞業務物件設計服務可確保服務內部功能高度相關,提升內聚性。提供具體的業務功能讓服務的邊界更清晰,有利於業務團隊和技術團隊的協作與溝通。

例如,線上商城系統的”交易服務”專注於訂單確認、下單和支付等功能,不應處理使用者認證、商品推薦等其他業務。

服務化架構的價值

面向服務的架構最大的價值就在於它的敏捷性和靈活性。

敏捷性體現在服務可以快速調整,獨立演進。靈活性體現在每個服務都有清晰的業務邊界,功能內聚性強,能夠單獨管理生命週期。具體來說:

  1. 輕量級應用:採用基於服務設計的輕應用,各個服務獨立開發、部署和運營,可以獨立交付,影響範圍小,提升交付效率。
  2. 服務複用、靈活編排:透過服務的複用和靈活編排,可以快速響應業務的變化,支援複雜的業務流程。
  3. 區域性擴充套件性高:系統被拆分為獨立的服務後,容易進行橫向擴充套件,只需要擴充套件必要的部分,成本更低,效果更好。

示例:訂單履約應用服務劃分

如圖所示,訂單履約能力是零售企業業務能力地圖中的 L2 級別業務能力。

訂單履約能力可以細分為多個末級業務能力:面向消費者的履約服務、訂單派單、訂單管理、揀貨管理、發貨管理和逆向履約。

基於這些末級業務能力,我們就可以設計出對應的應用服務。

應用結構設計

在完成應用服務的設計後,我們需要深入地瞭解應用的內部結構。

應用結構設計是把應用服務的概念轉化為具體實現的關鍵步驟。它描述了應用服務內部的層次結構和組織關係,決定了系統的模組化程度,以及後續開發和維護的難度。

應用結構的抽象層次

在設計應用結構時,我們通常會把系統分成不同的層次,比如系統級、應用級、模組級和程式碼級。

這種分層的方式有助於我們在不同層面處理複雜問題,確保系統結構清晰、易於維護。如圖所示:

  • 系統級:關注各個系統的整體佈局和管理方式,比如系統之間的關係,以及它們如何協同工作。
  • 應用級:聚焦每個應用的整體架構,包括應用與其他應用的互動方式,以及它們在整個系統中的角色。
  • 模組級:對應用內部進行更細緻的劃分,涉及程式碼的模組化設計、資料和狀態的管理等。透過合理的模組劃分,可以提高程式碼的可維護性和可重用性,減少重複工作。
  • 程式碼級:關注程式碼本身的結構和實現方式,這一層的設計直接影響到程式碼的質量和實現細節。

抽象層次的存在,是為了幫我們更有效地管理系統的複雜性。

透過把系統分成不同的抽象層次,我們可以更好地組織和理解系統結構,簡化開發過程,提高程式碼的可維護性和可擴充套件性。

這種分層方法讓開發團隊能在不同層次上專注於特定的問題,更好地應對大型軟體系統的挑戰。具體來說有以下作用:

1、分解複雜度

如果把所有的業務細節、技術細節都混在一起,整個系統就會變得難以理解、維護和擴充套件。透過設定不同的抽象層次,我們可以把系統的複雜性分解到各個層次,每個層次只需關注特定的功能和職責。

這種分層處理方式讓開發人員在專注於系統某一部分時,不用過多關注其他部分的細節,大大簡化了系統的設計和開發過程。

2、團隊協作邊界清晰

在大型專案中,通常會有多個團隊同時開發。如果系統沒有明確的邊界,各團隊之間很容易產生衝突和重複勞動。

透過清晰的抽象層次劃分,不同團隊可以專注於系統的不同層次或模組,互不干擾。

3、擴充套件性強

隨著業務需求的變化,系統往往需要不斷地擴充套件和升級。如果系統的架構設計沒有合理的抽象層次,擴充套件和升級就會變得非常困難,甚至可能需要對系統進行全面重構。

而在有抽象層次的系統中,變更通常只需聚焦在特定的層次上進行,而不會影響整個系統。比如,一次業務改動隻影響模組級別,我們就可以在不改變系統整體架構的情況下,替換或新增某個模組,滿足新的業務需求。

應用結構如何劃分?

在前面,我們提到了應用服務的設計方法,那麼怎麼把這些應用服務一步一步地轉化成程式碼結構呢?

其實,應用服務是透過一系列的應用結構來實現的。如圖所示。

基於應用服務的劃分,我們可以進一步細化應用結構,更好地組織和管理系統功能。這個過程涉及到多個層次的設計方法:

  1. 系統和子系統的劃分要和業務域、業務子域的粒度保持一致。 這樣,我們就能更好地把業務需求對映到技術實現上。
  2. 一個或多個相關的應用服務,可以組合成一個可獨立部署的應用。
  3. 在應用內部,可以進一步分層。 比如,參考領域驅動設計(DDD)的分層方法,可以分為使用者介面層、應用層、領域層和基礎設施層。
  4. 應用的各個層級內部,還可以細分為多個模組,每個模組又包含多個程式碼單元。

那麼,具體來說,我們該怎麼劃分應用呢?可以參考以下幾點原則:

1、業務劃分原則

應用劃分的關鍵是看應用服務的邊界。

應用服務的核心目標是幫助企業實現業務能力,所以它們需要和業務能力保持一致。而應用是實際的物理部署單元,應用服務最終要部署在特定的應用上。

因此,一個或多個相關的應用服務,可以組合成一個可獨立部署的應用。

應用服務可以單獨部署,也可以多個服務合併部署。那麼,如何判斷何時選擇獨立部署,何時選擇合併部署呢?這需要參考技術層面的成本和穩定性風險等因素。

2、技術劃分原則

在業務初期,儘量從單體應用開始,避免過早地把應用拆得太細,這樣可以減少分散式資料庫事務和資料不一致的問題,並可以降低技術部署成本。然而,即使在單體應用內部,也需要將應用服務劃分為界限分明的模組。

避免應用之間出現迴圈依賴或雙向依賴。在應用單元內部,可以進一步分層,始終保持不同層級之間的單向依賴關係,高層級可以依賴低層級,同層級之間不應互相依賴。

只有當真正遇到技術上的痛點,比如規模、效能、安全等問題時,才考慮拆分應用。如果不拆分會嚴重影響業務的穩定性,那就應該拆分。但不要為了拆分而拆分,因為每次拆分都會增加系統的複雜度。

3、組織規模原則

劃分後的單個應用的專案團隊規模通常建議保持在10~12人左右。

因為團隊成員越多,溝通的渠道就會成倍增加,可能導致資訊傳遞變慢或者失真。一個10到12人的團隊,可以確保大家的溝通更直接、更高效,減少資訊障礙。

小團隊更容易管理,專案經理或者團隊領導能更好地瞭解每個成員的工作狀態和需求,進行更有效的協調和支援。

同時,小團隊有助於建立更緊密的合作關係,成員之間更容易培養出默契,提升整體工作效率和專案質量。

5.5.5 示例:訂單履約系統的應用結構劃分

如圖所示,是訂單履約系統的應用結構劃分。

  • 使用者介面層:直接與使用者互動的層級,負責向使用者顯示資訊,響應使用者命令。
  • 應用層:定義軟體的應用功能,它負責接收使用者請求,協調領域層能力來執行任務,並將結果返回給使用者,核心模組包括:
  • 領域層:業務邏輯的核心,專注於表示業務概念、業務狀態流轉和業務規則,沉澱可複用的服務能力。

應用互動設計

應用互動就是指不同應用之間怎麼“溝通”和“交流”。

在一個複雜的系統中,各個應用可不是孤立存在的,它們需要互相配合,才能完成更復雜的業務流程。

應用互動的設計,就是為了確保這些系統和元件能夠順暢地“對話”,一起實現系統的整體目標。

應用互動的方式有很多種,包括同步呼叫、非同步訊息通訊等等。每種方式都有特定的應用場景和優缺點。

透過合理的互動設計,系統中的各個部分能夠高效協作,降低耦合度,增強系統的靈活性。

同時,好的互動設計還能顯著提升系統的效能和容錯能力,即使在高併發、大流量的情況下,也能保持穩定執行。

應用服務的上下游

應用服務是系統對外提供的核心業務功能。

雖然應用服務可以獨立發展和演化,但它們必須相互互動,才能實現整個系統目標。

那麼,如何設計應用服務之間的互動呢?首先,我們需要了解服務的上下游概念。

1、服務上下游的概念

服務的上下游關係可以透過DDD(領域驅動設計)的建模方法來定義,主要涉及“限界上下文”(bounded context)和“上下文對映”(context mapping)這兩個概念。

上下游表示上下文之間的對映關係,下游需要了解上游的領域知識來實現業務,而上游不需要了解下游。

換句話說,上游服務不需要關心下游服務的存在,但下游服務的實現卻依賴於上游服務提供的能力。

這個概念聽起來有些抽象,確實讓許多人犯迷糊。讓我們透過線上商城的幾個應用服務來具體說明:

  • 使用者服務:管理使用者的賬戶資訊,包括註冊、登入、認證、個人資料等,處理使用者的許可權和角色管理。
  • 商品服務:管理商品的基本資訊,包括名稱、描述、價格、圖片、分類等,提供商品的查詢、篩選和瀏覽功能。
  • 庫存服務:管理商品的庫存數量,處理庫存的預佔、扣減和回補操作。
  • 交易服務:處理訂單的建立、修改、取消和查詢,管理訂單的狀態和生命週期。
  • 支付服務:處理支付事務,支援多種支付方式,管理支付狀態。
  • 履約服務:處理訂單的履約,包括揀貨、包裝、發貨等,管理物流資訊和配送狀態。

如圖所示,我們可以看出各個服務的上下游關係。

商品服務和使用者服務是上游服務,它們提供基礎資料,其他服務依賴這些資料。

交易服務位於中間位置。對於使用者服務和商品服務來說,交易服務是下游,因為它依賴這兩個服務的基礎資料。

對於庫存服務來說,交易服務也是下游,因為交易下單過程中,需要庫存服務來預佔、扣減庫存。

對於履約服務而言,交易服務是上游,因為它提供訂單資料,驅動後續的訂單履約流程。

2、為什麼要區分上下游?

區分上下游關係的核心目標是為了解耦。

"解耦"這個詞相信大家都不陌生,但它的含義往往過於抽象和模糊。在這裡,我們探討一下解耦到底指什麼。

耦合是指兩個或多個結構之間的相互作用和影響。在軟體開發中,這可以理解為不同模組、系統或團隊之間的相互依賴和影響。

隨著業務需求越來越複雜,單個系統或團隊很難獨立實現所有功能。因此,解耦的目的並不是完全消除耦合,而是減少不必要的依賴關係。

前面提到,上游服務不需要關心下游服務的存在,但下游服務的實現卻依賴上游服務提供的能力。

因此,當下遊服務的團隊在迭代新功能時,無需評估是否影響上游服務,因為基於明確的上下游關係,可以快速判斷不會影響上游服務。只需評估是否影響自己的下游服務。

比如,交易服務的功能發生變更時,只需通知履約服務的團隊,評估是否會影響到他們,上游服務團隊則無需知曉。

這種方式能大大減少影響面的評估工作,提高團隊協作效率。

相反,如果上下游關係混亂,存在各種迴圈依賴,那麼任何一個服務的改動都難以準確評估影響面。此時,就需要召集所有服務的團隊,逐一評估是否有影響。

在實際場景中,如果每次專案會議都需要拉一大群人才能評估出影響面,這樣的協作效率就太低了。

3、上下游關係的核心使用場景

在軟體研發過程中,上下游關係在很多關鍵場景中都發揮著重要作用:

  • 明確服務之間的依賴關係:上下游關係讓開發者清晰地瞭解服務間的依賴。這有助於減少不合理的依賴,確保服務的獨立性和模組化設計。同時,它也避免了服務間的迴圈依賴,降低了一個服務出現故障引發連鎖反應的風險。
  • 評估影響面:當上遊服務變更時,可以預見其對下游服務的影響,從而制定相應的應對策略。
  • 指導團隊協作:上下游關係有助於明確各團隊的職責和工作範圍。上游團隊需要考慮下游團隊的需求,提供穩定的介面和服務;下游團隊則需要適應上游的變化。

應用服務的互動方式

應用服務之間的互動方式有很多,最主要的就是同步呼叫和非同步訊息。

1、同步呼叫

同步呼叫是一種通訊方式,呼叫方(客戶端)向被呼叫方(服務端)傳送請求,然後等待服務端處理完再返回結果。

在這期間,呼叫方會被“堵住”,直到收到服務端的響應。這種方式要求雙方都線上,而且呼叫方在等待響應時,沒法做別的事。

在微服務架構中,常用的同步呼叫協議包括 HTTP、REST API、Dubbo、Thrift、gRPC 和 SOAP 等。

同步呼叫適用於下游服務需要立刻從上游服務獲取資料或功能的場景。這種方式簡單直接,但需要處理服務之間的可用性問題。

舉個例子,使用者下單時,訂單服務需要同步呼叫商品服務,獲取商品的最新價格和庫存資訊,確保訂單有效。

通常來說,上游服務不應同步呼叫下游服務。如果上游服務同步呼叫下游服務,會導致上游需要了解下游的領域知識,違背DDD上下游的設計原則,加深系統耦合,並增加團隊協作複雜性。

此外,這種做法還可能引發級聯故障,降低系統可靠性。如果上下游直接互相呼叫,那下游服務發生故障時,也將直接影響上游服務的可用性,可能導致整個系統都癱瘓。

2、非同步訊息

非同步訊息是另一種通訊方式,訊息的傳送者(生產者)和接收者(消費者)透過訊息佇列或訊息中介軟體進行通訊。

傳送者發完訊息就可繼續其他操作,不用等接收者處理完。訊息被髮送到訊息佇列後,接收者從佇列中非同步獲取並處理。這樣一來,傳送者和接收者的處理時間就不耦合了,雙方可以各自獨立運作,提高了系統的靈活性和可擴充套件性。

在微服務架構中,非同步訊息通常透過訊息中介軟體實現,比如 RabbitMQ、Kafka、RocketMQ 等。

非同步訊息適用於上游服務向下遊服務釋出事件或通知的場景,能有效解耦服務,提高系統的彈性和可靠性。下游服務也可以透過非同步訊息向上遊服務反饋資訊,實現雙向通訊。

比如,使用者提交訂單後,訂單服務呼叫支付服務發起支付。使用者完成支付後,支付服務釋出一個“支付成功”的訊息,訂單服務接收到訊息後,更新訂單狀態併傳送通知。

3、其他互動方式

1)共享資料庫方式

多個服務訪問同一個資料庫,直接讀取或寫入資料。

在微服務架構中,通常不建議採用共享資料庫的方式,因為這違反了服務自治的原則,增加了服務之間的耦合度。

2)檔案傳輸

服務之間透過共享檔案系統或 FTP 等方式交換資料檔案。這種方式一般適用於批處理的場景,實時性較差。

3)服務匯流排(ESB)

使用統一的通訊匯流排來連線不同的服務和系統。服務之間不直接通訊,而是透過匯流排來“中轉”,適用於需要整合多種異構系統和服務的大型企業級系統。

但是,這種方式引入了額外的架構層,增加了系統的複雜性。所有服務都耦合到匯流排上,存在單點故障的風險。

本文已收錄於,我的技術網站:tangshiye.cn 裡面有,演算法Leetcode詳解,面試八股文、BAT面試真題、簡歷模版、架構設計,等經驗分享。

相關文章