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

前言前端


近年來,服務化和微服務的架構隨着線上業務對響應變化和發佈頻率要求的不斷提升已經變得日益常見。像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時充分利用這些協議特性,可以使服務變得更加易用。


尾聲

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

相關文章
相關標籤/搜索