如何設計一個基礎服務?看完這篇文章別再說不會、不懂、不知道

最新互聯網大廠面試真題、Java程序員面試策略(面試前的準備、面試中的技巧)請移步GitHub


咱們知道,落地一個微服務其實並不困難,但要實現一個可以高度複用的共享服務並不容易,在落地過程當中,常常會有一系列的問題困擾着咱們。git

咱們事先對服務的邊界沒有進行很好的劃分,結果在落地的過程當中,你們反覆爭論具體功能的歸屬。程序員

因爲對業務的瞭解不夠深刻,咱們要麼設計不足,致使同一個服務有不少版本;要麼服務過分設計,實現了一堆永遠用不上的功能。github

對於落地一個共享服務來講,服務邊界的劃分和功能的抽象設計是核心。服務邊界肯定了這個服務應該「作什麼」,抽象設計肯定了這個服務應該「怎麼作」。面試

接下來,我就以一個實際的訂單服務例子,爲你詳細講解一下要如何重點解決這兩個問題。這樣你能夠經過具體的案例,去深刻地理解如何落地共享服務,實現業務能力的複用。小程序

訂單業務架構

不一樣企業的訂單業務是不同的,因此這裏我先介紹下這個訂單的業務場景。架構

這是個 O2O(Online To Offline,線上到線下)的交易業務,訂單的來源有兩個,一個是自有小程序或 App 過來的訂單,還有一個是外賣平臺過來的訂單,而後這些線上的訂單會同步到門店的收銀系統進行接單和進一步處理。這裏我放了一張訂單的業務架構圖,你能夠到文稿中看下:異步

如何設計一個基礎服務?看完這篇文章別再說不會、不懂、不知道

在這裏,訂單服務是和 4 個應用直接打交道的:ide

  • 小程序服務端調用訂單服務落地自有線上訂單;
  • 外賣同步程序接收三方外賣平臺的訂單,而後調用訂單服務落地訂單;
  • POS 同步程序經過訂單服務拉取訂單,並推送給商戶內部的收銀系統;
  • 最後還有一個訂單管理後臺,經過訂單服務查詢和修改訂單。

OK,接下來,咱們就具體看下,如何從頭開始落地這個訂單服務。微服務

訂單服務邊界劃分

首先,咱們要肯定這個服務的邊界,這是進行服務內部設計的前提。劃分邊界時,你須要對相關的業務場景有充分了解,而且在必定程度上,可以預測潛在的需求。性能

根據業務場景的分析,這個訂單服務須要負責三個方面的功能。

1. 基本信息管理

首先是訂單基本信息管理,主要提供訂單基礎信息的增刪改查功能,包括下單用戶、下單商品、收貨人、收貨地址、收貨時間、堂食或外賣、訂單狀態、取餐碼等。

另外,你須要注意的是,這裏有多個下單渠道,除了通用的訂單信息,每一個渠道還有特定的渠道相關信息,好比堂食的訂單要有取餐碼、外賣的訂單要有收貨人和收貨地址等等,這個都須要在咱們的數據模型裏給出定義。

2. 訂單優惠管理

而後是訂單優惠管理功能,這對應的是訂單的小票信息,從最開始的商品金額,到最後須要用戶實際支付的金額,中間會有一系列的折扣和減免,這些都是屬於訂單信息的一部分。這些信息咱們須要展現給用戶看,若是後續要進行訂單成本的分攤,也須要用到它。

3. 訂單生命週期管理

最後是訂單的生命週期管理功能,主要負責管理訂單的狀態變化。咱們知道,從不一樣下單渠道過來的訂單,它的狀態變化過程是不同的;不一樣行業的訂單,它的狀態變化過程也是不一樣的,因此訂單服務的狀態要作到通用,可以支持各類可能的狀態定義和狀態轉換過程。這個也是訂單服務設計的難點,我在後面會重點介紹。

好了,如今咱們已經給出了訂單服務的功能。爲了更好地定義邊界,在實踐中,你還須要澄清哪些功能不屬於服務,這樣能夠避免後續的不少爭論。因此在這裏,我會進一步給出訂單

服務不包括的功能,你在劃分本身的服務邊界時最好也可以明確給出。

第一,做爲基礎服務,訂單服務不主動調用其餘服務

好比說,你想了解訂單的用戶詳情、商品詳情等等,這應該由上層應用經過調用相應的服務來實現,而後和訂單信息組裝在一塊兒,而不是在訂單服務內部直接調用其餘服務,不然會致使基礎服務之間相互依賴,職責模糊。

若是說這個信息整合的場景很是通用,咱們能夠建立一個在基礎服務之上的聚合服務來實現,把訂單信息、用戶信息、商品信息整合在一塊兒。

第二,訂單服務不負責和第三方系統的集成

在這裏,訂單須要在咱們的訂單服務和三方外賣平臺,以及收銀系統之間進行同步,這些同步功能都是針對第三方系統定製的,不具備通用性。而咱們的訂單服務做爲基礎服務,須要具有通用性,所以這些和外部系統對接的功能不會在訂單服務的內部實現,而是由額外的同步程序實現。

小提示:這些同步程序能夠主動調用訂單服務,而後再和第三方對接,若是想實時獲取訂單信息的變化,同步程序能夠訂閱訂單服務的消息通知,第一時間瞭解訂單變化。

第三,訂單服務不提供優惠計算或成本分攤邏輯

訂單服務不負責具體的優惠計算,只提供優惠結果的存儲和查詢,用於還原訂單的費用組成。優惠的具體計算過程通常由專門的促銷系統負責,成本的分攤通常由後續的財務系統負責。這個咱們在上一講中已經說過,這裏就不詳細解釋了。

最後,該服務不提供履單詳情,不負責詳細物流信息的存儲

好比說,訂單已經發送至上海、訂單已經到達某某快遞站等等這些信息,訂單服務不負責提供這些詳細信息,這些都是屬於後續履單系統的職責。訂單服務能夠存儲一些外部系統的單據號碼,好比配送單號,這樣能方便上層應用經過訂單記錄和配送系統進行關聯,獲取配送的詳細信息。但訂單服務只負責存儲,不負責數據的進一步解釋。

到這裏,你能夠看到,經過從正反兩個方面說明訂單服務的職責,咱們就獲得了一個邊界很清晰、職責很聚焦的訂單服務邊界,全部人對它的職責認識是一致的,儘量地避免了後續的爭論。

訂單服務內部設計

好,肯定了這個訂單服務要作什麼以後,接下來,咱們要解決的就是服務內部怎麼作的問題了。

做爲共享服務,咱們要保證訂單服務功能上的通用性,就須要同時對內部數據模型和外部接口進行良好的抽象設計。

1. 訂單狀態通用化

對於數據模型來講,訂單要存儲哪些信息,已經比較明確了,具體你能夠看下這個圖。

如何設計一個基礎服務?看完這篇文章別再說不會、不懂、不知道

但對於如何管理訂單的狀態,狀況就比較複雜了。

咱們知道,若是針對一個具體的項目,不管它的訂單狀態有多麼的複雜,咱們均可以事先精確地定義出來。但不一樣的行業甚至不一樣的企業,他們對於訂單狀態管理都是不同的,訂單服務做爲一個共享服務,它必需要知足不一樣項目的訂單狀態管理。因此對於如何解決這個問題,這裏我有兩個思路供你參考。

一個是開放訂單狀態定義

在這裏,訂單服務事先不限定訂單有哪些狀態,每一個項目均可以本身定義有哪些訂單狀態。服務的調用方能夠在接口裏傳遞任意的狀態值;訂單服務只負責保存狀態數據,不負責解釋具體的狀態,也不負責任何的規則校驗,它容許訂單從一個狀態轉換爲其餘任意的狀態。

這樣的設計,在理論上能夠知足各類狀態的定義,知足各類狀態之間的變化,但這樣作其實有很大的問題。在這裏,訂單狀態是徹底由外部負責管理的,上層應用的負擔會很重,不但要負責定義有哪些狀態,並且還要維護狀態的轉換規則,一不當心,訂單可能從狀態 A 非法地變成狀態 B,致使業務出問題。

另一個是應用和服務共同管理狀態

對於訂單狀態管理,應用和服務各自承擔一部分職責,咱們看下具體如何實現。

咱們知道,不管訂單的狀態變化是如何的複雜,咱們老是能夠定義一個訂單有哪些基本的狀態,包括這些基本狀態之間是如何變化的。好比,訂單一開始都是用戶下單後待支付,支付完成後變成一個有效的訂單,而後由商家進行接單,製做完成後進行發貨配送等等,訂單最終的狀態要麼是完成,要麼是取消。

這些訂單的基本狀態,咱們稱之爲「主狀態」,它們由訂單服務負責定義,包括這些主狀態之間的轉換規則,好比已完成的訂單不能變爲已取消的訂單。主狀態的數量是比較有限的,狀態之間的變化關係也是比較明確的。

這個主狀態,咱們對大量現有的業務場景進行總結和抽象,是徹底能夠定義出來的。在這個訂單服務例子裏,咱們定義了以下圖所示的訂單狀態機,包括有哪些主狀態,以及它們的轉化關係。

如何設計一個基礎服務?看完這篇文章別再說不會、不懂、不知道

訂單除了「主狀態」,還有「子狀態」

好比,一個訂單處於配送中,實際狀況多是「倉庫已發貨」,「貨已到配送站」,或者是「快遞員正在送貨中」等等,那麼在這些狀況中,訂單的主狀態都是「配送中」,它的子狀態就是細化的這幾種狀況。子狀態有哪些具體的取值,不一樣的項目是不同的,這個就開放給各個應用來定義

因此,訂單服務數據模型裏有兩個字段,其中的主狀態由訂單服務負責管理,包括主狀態之間的變化規則;而子狀態由上層應用來定義,管理子狀態的變化規則,好比一個配送中的訂單,它的子狀態能夠由「倉庫已發貨」,變爲「快遞員正在送貨中」。

如今,咱們就能夠總結下這兩種訂單狀態的設計思路。

第一種方案,咱們不對訂單狀態進行管理,而是把訂單的狀態做爲一個簡單的屬性存儲,只支持訂單狀態簡單的增刪改查功能。咱們知道,訂單狀態是訂單業務規則的核心體現,這樣的訂單服務是沒有靈魂的,也失去了大部分業務複用的價值。

第二種方案,應用和服務共同管理訂單的狀態,訂單服務抓大放小,經過主狀態管理把控住了訂單的核心業務規則,同時把子狀態開放給應用進行管理,爲具體的業務場景提供了靈活性。經過主狀態和子狀態的結合,訂單服務就知足了不一樣行業、不一樣企業的訂單狀態管理需求。

2. 訂單服務接口定義

說完了訂單的狀態管理,接下來,咱們從調用方怎麼使用服務的角度,來看下訂單服務外部接口是如何設計的。

外部系統和服務的交互有兩種方式,包括同步的服務接口調用和異步的消息通知。

首先是同步的服務接口調用

爲了方便外部調用方,咱們在服務接口命名時,必定要規範和統一,接口名字要可以望文生義,方便調用者快速找到所須要的接口。而且,咱們還要提供接口具體的請求和響應樣例幫助說明。

具體的接口設計規範,我就不具體展開了,每一個公司都要有明確的規範要求,這裏我就說下常見的查詢接口是如何設計的。

一個訂單有不少字段,每次調用方要查詢的信息可能都不相同,不一樣字段之間的組合方式有不少,咱們不可能一一支持。

那麼,咱們怎麼設計查詢接口,來知足各類場景需求呢?通常來講,咱們能夠根據返回字段數量的不一樣,提供三個不一樣粒度的查詢接口來知足多樣化的需求。

第一個是粗粒度接口,只返回訂單最基本的 7-8 個字段,好比訂單編號、訂單狀態、訂單金額、下單用戶、下單時間等等;第二個是中粒度接口,返回訂單比較經常使用的十幾個字段;第三個是細粒度接口,返回訂單的詳細信息。

這樣,不一樣的查詢需求,就能夠根據要返回信息的詳細程度,來選擇合適的接口,經過這種方式,咱們兼顧了要定義的接口數量和查詢的性能。

其次是異步的消息通知

訂單服務除了提供同步的接口調用,還針對每次訂單信息的變化,提供異步的消息通知,感興趣的外部系統能夠經過接收消息,第一時間感知訂單的變化。

按照消息詳細程度的不一樣,訂單消息能夠分爲「胖消息」和「瘦消息」。

顧名思義,胖消息包含了儘量多的字段,但傳輸效率低;瘦消息只包含最基本的字段,傳輸效率高。若是外部系統須要更多的信息,它們能夠經過進一步調用訂單服務的接口來獲取。

在這個訂單服務的例子裏,若是是訂單狀態的變化,咱們只需提供訂單號、變化先後的狀態便可,所以主要以瘦消息爲主;若是是新訂單的建立,因爲訂單的字段比較多,因此使用胖消息,避免外部系統進一步調用訂單服務接口。你在實踐中,能夠根據實際狀況,在消息的數據量和消費者處理消息的複雜度之間作平衡。

前面咱們說了,訂單服務不會主動調用外部系統的接口,這裏的異步消息通知,就能夠很好地保證外部系統及時感知訂單的任何變化,同時避免訂單服務和外部系統直接耦合。

總結

要想打造一個可高度複用的共享服務,你須要掌握最核心的兩點:清晰的邊界劃分、內部的抽象設計

今天,我經過一個實際的訂單服務例子,幫助你理解如何清晰地定義服務的邊界,以及如何經過抽象設計保證服務的通用性。你在實踐中,必定要深刻分析業務場景,識別真正的挑戰在哪裏,避免設計的簡單化或過分複雜化。

相關文章
相關標籤/搜索