1. 程式人生 > >業務視角下的微服務架構設計例項

業務視角下的微服務架構設計例項

作者:林帆,ThoughtWorks諮詢師
責編:魏偉,歡迎雲端計算、大資料領域技術投稿、約稿、給文章糾錯,請傳送郵件至weiwei#csdn.net(#改為@)
本文為《程式設計師》原創文章,未經允許不得轉載,更多精彩文章請訂閱《程式設計師》

導讀:本文以某汽車產品代理商的線上銷售和售後平臺為例,介紹它在微服務轉型過程中遇到的種種情況,希望能以此作為前車之鑑,讓後來者避開不必要的趟坑。

前言

近年來,服務化和微服務的架構隨著線上業務對響應變化和釋出頻率要求的不斷提高已經變得日益常見。像DevOps和Docker等理念和技術的成熟也在為這一趨勢推波助瀾。許多企業對微服務思考的關注點也從最初的“該不該用”、“該如何用”逐漸轉向“如何評價服務拆分的好壞”、“服務拆分後的資料如何治理”這樣更加具體而實際的方面。縱觀社群裡與微服務有關的話題,既有一些適合大眾閱讀的概念入門科普文章,也有一些像“架構去中心化”、“訊息非同步解耦”、“最終一致性補償”這類專業性的技術討論,但卻很少看到能夠比較深入的介紹微服務實施經驗,說清楚“什麼地方會有什麼坑”這類問題的實踐性內容。

若將微服務的方方面面鋪開,將是一個非常龐大而複雜的知識體系。萬事開頭難,如何邁出服務劃分的第一步是對於那些從未採用過微服務的專案首先要面對的問題。雖然服務的劃分本來是一件十分憑藉架構師個人經驗和對業務理解的主觀工作,但其中仍然有些規律可循。根據服務的型別特點,可以有以下幾種常見的方式:

  1. 根據業務進行建模,依據業務領域的邊界劃分,這也是當下微服務社群十分推崇的領域驅動設計(Domain Driven Design,簡稱DDD)方法
  2. 根據資源使用型別劃分,這種方式主要使用在對硬體資源需求很高、或對特定硬體型別/區域地址等存在依賴的大型服務系統,例如系統的某些功能非常消耗CPU、或是某些功能必須在有加密狗裝置的機器上執行。此時可以依據系統各部分對不同資源的需要作為邊界進行最安全的劃分,以最大化執行效率
  3. 根據資料邊界劃分,直接從資料表結構或資料來源著手,依據資料歸屬關係界定服務。這種劃分方法簡單粗暴且能避免資料耦合,但容易導致潛在隱患,甚至可以被認為是一種反模式。僅僅對於強資料驅動的系統,如某些報表系統和多資料來源ETL系統等適用,在實際案例中很少見

從現實上來說,我們平時遇到的絕大部分系統都是需要為特定終端使用者群體服務的。因此,採用基於業務領域的建模的方法通常都會是個不錯的選擇,它也是目前在劃分服務問題中的最主流的方法。不過即使有了理論指導的支撐,服務劃分裡的利弊權衡並不是非黑即白的事情,依然存在不少模稜兩可之處,只有在犯下一些錯誤後才能摸索出適合自己系統的方法。本文將會聚焦在這些點。

沒有什麼能比一個具體而貼近真實的案例更具有代表性了。因此,我們將剖析一個傳統行業企業的線上業務:某汽車產品代理商的線上銷售和售後平臺,介紹它在微服務轉型過程中遇到的種種情況,希望能以此作為前車之鑑,讓後來者避開不必要的趟坑。這個案例中講述的場景原型並非完全來自同一個專案,而是從我們過去一年中所經歷的多個專案的實際場景抽取出來的,去除了其中的敏感資訊,並添加了基於真實情況的適當演繹,使之更加完整。簡單介紹一下案例的背景:這是一家頗具規模的汽車代理商企業,承接多個國際一線品牌汽車的銷售和周邊資源整合的業務,具有龐大的實體店網點。其線上的IT業務系統已經存在了十餘年,提供的功能從最初的進銷存管理、客戶資訊管理等到現在的面向消費者客戶的服務系統,十分複雜。系統中的線上銷售和售後平臺部分是相對比較新、且需求變化特別迅速的部分,也是目前出問題最頻繁、收到抱怨最多的部分。

下面我們將從幾個方面展開這個話題。

不要從資料庫開始建模

傳統軟體開發中,資料模型被認為是整個系統的核心,業務邏輯僅僅是對資料的CRUD加上簡單的計算呈現。有些專案團隊的架構評審會花很多時間來討論系統龐大的ER圖(實體關係圖)設計。但在微服務架構設計時,ER圖並非最佳選擇,特別是在服務劃分時採用ER圖進行建模甚至是十分有害的。

在領域驅動設計的實踐中,有一個和ER圖建模有些相似的環節,叫做“領域建模”。相比ER圖建模通常只有開發人員參與,並從資料表的角度考慮模型的方法,領域建模的過程需要由產品的業務人員和核心開發人員共同參與,先梳理使用者場景,然後從業務領域角度,逐步確定場景中的實體、關聯和聚合等元素。其中的“實體、關聯”與ER圖中的“實體、關係”有些相似,但含義並不一致。領域模型中的“實體”本質上是業務場景中需要被持久化儲存的物件,但儲存方式不一定是資料庫,更不一定是關係型資料庫,而ER圖中的“實體”最終對應的就是關係型資料庫的表。領域模型中的“關聯”是兩個實體在業務上下文之間有業務含義的聯絡,而ER圖中的“關係”只的是兩張表之間的一個關聯外來鍵。

那麼,使用ER圖給微服務建模會存在什麼問題呢?

首先,ER圖設計時假定了使用的是關係型的資料庫。微服務的一個特點在於它支援異構架構,系統的不同部分,依據實際需要可選擇不同的程式語言、框架以及資料庫的型別。關係型資料庫採用平面表的結構,如果有兩類巢狀關係的物件,只能使用關聯表來表達兩個實體之間的關係,然後通過複雜的SQL語句在查詢時將多個表拼成更大的平面表,最後在業務程式碼裡再分解到各個獨立的物件裡去。這些單獨的表又可能在其他地方與另一個表存在關聯查詢。這樣設計出來的表結構的冗餘很低,且使用非常靈活,但正是這種靈活性往導致系統中多個業務邏輯表出現錯中纏繞的關係,從而使剝離單獨服務進行異構設計變得困難。

其次,ER圖還會導致實體相似的不同業務邏輯在設計時被耦合在一起。舉一個具體的例子,在汽車代理銷售系統中,不同品牌汽車的購買流程後端實際上分別對接的是完全不同的分銷渠道系統和邏輯流程,但它們在使用者檢視上所需要的資訊比較一致,因此儲存的資料結構也比較相似。這個系統在最初設計時採用了ER圖的方式建模,由於ER圖模型不關心業務層面上的東西,不同品牌汽車的實體資料看上去都是一種型別的資料,僅僅是一個品牌欄位的差異而已,因此被理所當然的設計成了同一張表。上層邏輯實現的時候使用了大量的if-else語句來區分各種品牌的購買流程,結果使得多條完全不同的業務線糅合在同一個上下文裡,後來的開發非常容易在這裡錯改、漏改程式碼。若當初使用的是領域建模,則不同的購車流程會自然的被劃分到各種不同的使用者場景,即使它們在資料結構上看起來基本相同,也會被識別為兩個獨立的上下文,這就會使得未來劃分服務時能夠更加容易。

最後,ER圖設計的架構會使得系統的模組之間傾向於使用資料庫整合,而非API整合。在ER圖中沒有明確劃分模組和表的所有權關係,所有的資料表對所有的模組都是可見的,倘若不加額外約束,各個模組便都會輕易的讀寫其中的內容。資料整合並不會直接導致服務無法拆分,但由於資料的所有權不清晰,十分容易引發的意想不到的狀況。還是舉銷售平臺的例子,在ER圖建模得到的模型中,有一張與購買記錄相關的表。在一次銷售業務的程式碼更新中,對購買結果的增加了欄位,在測試過程中沒有發現問題,結果上線幾天以後,由於售後服務也在修改這個表而導致出現了髒資料,造成難以排查的故障。

當然,我們並非要完全否認ER圖建模的價值,只不過通過資料庫角度建立模型的過程容易傾向於設計出龐大的單體應用,因而不太適應於服務劃分的目的。

端到端的劃分服務

在拆服務時要端到端的劃分,這是我們在設計微服務時經常聽到的一句話。端到端的劃分,指的是一個服務負責一個業務領域,這個功能領域的所有邏輯、資料都歸它管,這有利於微服務的資料治理。與之相對的是MVC那樣的橫向服務劃分,將所有資料歸一塊,所有邏輯歸另一塊,特別是在跨團隊管理的情況,橫向劃分服務會帶來十分高昂的協調和聯調成本。

道理不必多說,還是講個例子吧。在汽車銷售平臺的最初架構中,使用了典型的MVC三層結構,由於人比較多,分成了前臺組、後臺組,就時不時要出現新開發一個功能,一動底層資料結構,結果上層的另一個不相干頁面掛了,一查發現原來那這個頁面間接的用了同一個資料模型。比較典型的例子是,有一回負責後臺的小組調整了汽車銷售服務裡面的汽車引數資訊相關的物件結構,結果一上線,銷售功能正常,試駕服務的頁面掛了。原來是試駕功能的前端開發人員在處理汽車資訊時候,看到銷售模組有現成功能,就直接拿來複用了。這便是服務上下層分團隊開發導致的問題。

此外,橫向劃分服務也不利於系統的開發效率的提升。不同業務服務對功能釋出頻率的的需求是不一樣的,比如試駕平臺經常推出新的優惠促銷活動,需要儘快進行一次版本更新,於此同時售後服務有一個需要和第三方聯調的功能也已經差不多完成並提交到程式碼倉庫了,但由於需要協調第三方系統的時間,最近還不能夠上線,此時兩邊的業務主管就要開掐了。類似這樣的情況其實經常發生,通常使用特性分支、特性開關等流程或者技術手段能夠規避一部分業務開發進度不一致的風險,但若要從根本上解決這種問題,還是需要端到端的按業務來劃分服務。

最後,橫向劃分模組對於問題的追蹤除錯也不友好,幾乎每次事故調查總是要穿插涉及在幾個團隊之間不停的協調開會,因為一個完整業務流總是要貫穿前後幾個層的功能。

需要指出的是,端到端劃分服務並非是說服務與服務之間都是平級的。實際上,服務之間可以再聚合成更高層級的組合服務的,以及在最頂端的API Gateway也可以算是一類服務。只不過,在核心業務層的這些服務,每個都單獨提供了某項特定的業務價值。

識別核心業務服務

微服務的架構通常並非是一開始就重頭設計出來的,而是先有整塊的單體架構,隨著業務的複雜度上升,才逐步拆分出來。業務領域建模除了能用來指導適當的服務劃分,另一個重要的作用是讓工作聚焦到核心的業務服務中。

無論多複雜系統都是為特定業務價值而存在的。在系統的實現中必然會存在與核心業務最相關的部分、輔助核心業務的部分、和非核心業務關係的部分。它們在領域驅動設計的術語中稱為“核心域”、“支撐子域”和“通用子域”。在進行領域建模的時候就應該順便識別出系統裡的關鍵領域。將系統的業務領域羅列出來然後劃分出重要性,這件事情聽起來似乎是多此一舉,甚至有點荒唐,但對於複雜系統,實際去做這件事帶來的價值可能遠比它看起來更大。

首先,當我們將一個複雜的系統的各種業務仔細清點到檯面上以後,得到的列表往往會比許多人最初想象得長得多,它能提醒我們系統的複雜度是否已經過高了。其次,列舉業務的過程也是開發者與業務人員溝通的過程,來自不同業務線的代表也許會為某部分業務的價值點發生爭執,或是提供一些許多開發人員此前並不瞭解的細節資訊,將這些問題當面討論清楚並非什麼壞事。此外,當我們真的去仔細思考一個業務系統的核心價值時,也許會得出使人意想不到的結果。

繼續舉例子,在汽車的銷售和售後平臺中,通過業務建模,可以劃分出許多子領域:

• 線上購車(渠道A)
• 線上購車(渠道B)
• 線上購車(渠道C)
• 試駕預約
• 售後服務
• 使用者反饋
• 訂單系統
• 促銷活動
• 車輛市價資訊
• 車輛引數資訊
• 4s店資訊
• 使用者資訊管理
• …

(說明:這裡渠道指的是銷售平臺對接的一個第三方系統,其中每個渠道可以對應多個汽車品牌)

然後我們要從中識別出業務中的核心域。必須強調,服務領域劃分僅僅是代表服務對系統關鍵業務貢獻的價值,處於核心域中的那些服務應該是該系統業務成功的主要促成因素,從戰略層面講,企業應該在自己的核心領域上具有一定壁壘優勢。經過討論,開發者和業務人員最終得出讓人大跌眼鏡的結論,核心域部分的服務只有“試駕預約”和“售後服務”,因為這兩項才是該企業最具競爭力的業務。而看起來十分重要的“線上購車”服務,由於並不具有特別的行業競爭優勢而被劃到了支撐子域中。正確的服務劃分定位將對系統未來的發展策略產生積極的影響。

在這個系統中的“車輛市價資訊”、“車輛引數資訊”、“4s店資訊”等服務都被劃歸到了通用子域。在通用子域中的服務並非最沒有價值或是複雜度最低,而只是說明系統在這些服務領域中通常不具優勢,因此這部分功能完全可以考慮外包開發或者購買第三方的現成服務。

使用符合業務結構的API

前面介紹業務建模的時候我們強調了使用“API整合”的必要性,以及它的反面形式“資料庫整合”所帶來的問題。在微服務的架構中,服務的技術選型可能是異構的,API的實現也會各有不同。除了Web應用比較流行的RESTful標準,還有像SOAP、ProtolBuf、MessageQueue等不同的協議與格式標準,它們都可以被作為服務之間通訊的API。不同的API設計對服務使用的體驗差別會很大,除去技術原因對API協議的傾向性,在設計和評價API方面依然有許多值得注意的地方。

一個好的API設計應該在介面的元資料中向用戶提供儘可能多的有意義的業務資訊,這裡指的元資料包括例如API的名稱、引數、標籤等等使用者在不需要專門檢視文件就可以看到的內容。通常在各種不同的API協議裡,總會存在一個相似的概念,就是目標路徑。比如RESTful的URL地址,ProtolBuf的訊息型別巢狀結構,MessageQueue名稱裡用斜線劃分Topic路徑的慣例等。以RESTful標準為例,可以試比較下面兩種URL地址的差異。

• 第一種:/users/123/orders/123
• 第二種:/orders?id=123&user_id=123

顯然前一種地址包含的資訊更多,它告訴了訪問者訂單(orders)是屬於使用者(users)這個實體中的一個子實體,並且包含了一個潛在業務規則,即如果ID為123的這個使用者不存在了,那麼查詢他下面的所有訂單資訊也是不具有業務意義的。實體之間的巢狀關係可以通過領域建模過程中的聚合識別。當然,這種URL結構有時會導致很長的API路徑,但相比它所帶來的業務語義,我們認為還是值得的。

類似的語義化例子還有例如:

• PUT /services/questions/123
• PUT /services/questions/123/comments/123

這是售後服務部分的兩個API,分別用來更新提問的內容和提問的評論內容,它們也是按層級組織的。

有些通訊協議中還提供了其他可以表明業務語義的元素。比如RESTful中使用GET/POST/PUT操作語義(查詢/建立/修改),以及HTTP返回值中的語義資訊等。在實際設計API時充分利用這些協議特性,能夠使服務變得更加易用。

尾聲

在這篇文章裡,我們列舉了一些微服務劃分的實踐中常見的反例和值得注意的問題,希望能為讀者設計微服務架構時掃清一些阻礙。實際上,本文中提到的許多概念,包括微服務和領域驅動設計,服務的劃分都只是其中的冰山一角,在冰水之下還有更多的內容值得我們在實踐中去探索。

全天候聚焦IaaS/PaaS/SaaS最新技術動態,深度挖掘技術大咖第一手實踐,及時推送雲行業重大新聞,一鍵關注,總覽國內外雲端計算大勢!
圖片描述