服務應該去版本化,不管是微服務還是SOA
在閱讀本文之前,你需要先對前面的背景有些了解,下面是本系列的前兩篇文章:
經過前面的熱身,我相信后面兩章我們談S++不會那么生澀了,尤其是第一篇發表后一位叫lpy的架構師指出:“除非有必要,我們不要創造新的概念”,這就是著名的 奧卡姆剃刀定律“如無必要,勿增實體” 。本章主要談S++一些基本的特性和基礎應用,看看我們新增的基礎概念是否該被剃刀無情的剔除。然后我們在下一章重點探討S++如何解決微服務帶來的問題以及---SOA的終極目標也就是業務敏捷性。
我們先簡單回顧一下S++的定義: 服務的本質就是行為(業務活動)的抽象 。與SOA和微服務相比較,S++不僅僅進行了簡單抽象,更重要的是完成了服務的 業務與技術 分離 以及服務的 多態建模 。
那么,S++作為SOA的更高級版本,為了完成SOA的終極目標也就是業務敏捷性,必須具有以下幾個特征:
1、服務的業務要素必須唯一并不具有歧義
在S++中,通過引入 服務元數據 的概念來描述業務要素,服務元數據的引入是實現 業務與技術 分離的重要手段。通過服務元數據的全局唯一性來保證業務要素的全局唯一性,舉例說明如果兩個不同的服務中都包含元數據A的引用,那么在這兩個服務中的這個業務要素是一致的,所謂一致是指的在同一個業務流程的上下文中,這兩個字段的取值是相同的。下面大家來看個簡單的例子來理解一下如何用元數據來表達服務:
元數據定義表,元數據ID全局唯一,不可重復。
服務定義樹:
我們看到,由于元數據全局唯一,所以決定了要素一致的服務是相同的。
2、服務必須在空間和時間上具有唯一性和穩定性
服務要素元數據化后,元數據的 時空唯一性 就決定了服務在時空上也是唯一的。當任意兩個服務,如果其定義中引用的所有元數據集合完全一致的時候,無論這兩個服務的結構上有什么差異,這兩個服務都是同一個服務。
服務的 時空穩定性 是指,服務的一旦被嚴肅的定義出來,那么在任何使用環境中、任何時刻其已經明確的業務要素不會被改變,任何要素的改變意味著一個新的服務被定義。比如打球含有兩個要素:人和球,無論在任何時間任何地點打球都需要這兩個要素;假設某個場景需要去掉球這個要素,那么需要定義一個新的服務,這個服務可以只有人這個要素,但新的服務可能就是打架了。
3、服務需要具備多態性
服務多態性是指,服務可以在運行時刻動態的轉變為另外一個服務的特性。
服務的內涵
所有與服務的具體實現者無關的服務屬性,都屬于服務的內涵范疇。除了服務的基本信息之外,主要還包含三大類:
-
服務的SDA(Service Definition Agreement),服務的定義部分。
-
服務的SLA(Service Level Agreement),對服務提供者的服務水平的約束部分。
-
服務的OLA(Service Operation Agreement)的一部分,對服務的操作層面的描述。
從服務的內涵來看,服務具有唯一性,只要具有相同要素的服務,無論你是否愿意承認,他們都是一種行為。而且,服務不但具有空間的唯一性,還具有時間的唯一性,比如古代的餐飲服務,和現代的餐飲服務,其實在要素上幾乎沒有差別。我可以推測,馬車時代的修車服務和現代的修車服務在要素上也不會有什么差異,甚至可以產生完全一致的抽象。
服務的外延
自然地每一個服務具體的實現者都是服務的外延,為方便起見我們也可以把服務的消費者也看作服務的外延。每一個服務的外延都有各自的不同,事實上傳統大服務的WebService和微服務的RESTful都可以看作服務的不同外延,我們管這種外延叫服務的接口。
服務通過一致的內涵來統一服務的功能,通過多樣化的外延來適應不同的消費需求。其實,正是通過服務的多樣性外延我們才可以體驗服務在空間和時間上的一致性。在這里我們再次去體會第一章騎大象的例子,同樣一個事情,從不同的角度看就具有不同的要求。那么上面那個簡單的例子里,服務一樣需要外延來表達,比如下面的接口定義:
畫紅圈的部分就是一個特定的實現需要額外添加的技術要素,這些技術要素是實現者因為技術或管理原因要求的。比如序列號,是因為系統需要標識每一筆交易不能重復;柜員號是因為從管理上要求知道每一筆交易是哪個業務員操作的,以便未來的績效、查錯等。而這些技術要素就沒有綁定業務元數據,所以就不會和業務相混淆。
服務的多版本
傳統的,如果我們把WebServices或者RESTful當作服務來看的話,那么服務一定是有版本的。但是,根據S++對服務內涵的定義,服務具有空間和時間上的一致性,所以服務本身是不需要版本的。服務的變遷是因為我們對服務的抽象過程的缺陷造成的,一旦最新的抽象形成了,那么這個抽象必然完全滿足時間和空間上的所有外延實現(大家可以回顧打球和打架的例子)。這就是說,我們只需要保存最新的內涵抽象就夠了,那么歷史的版本在哪里呢?其實,歷史的版本在已經使用的外延中幫你保存了,當然為了更好的記錄變遷,你可以對服務的變更過程做一些批注記錄。
其實,傳統服務的多版本對SOA甚至是有壞處的,比如在服務組合流程當中。當我們認定服務在空間和時間上的唯一性后,服務的組合中調用的原子服務就具備唯一性,一旦版本被引入后破壞了服務在時間上的唯一性,那么服務組合就要決定其調用的原子服務在不同時間點的副本,這本身和業務流程毫無關系,從而破壞了業務流程的完整性造成流程不穩定。
服務的業務與技術分離
業務與技術分離是個老生常談的話題,在S++中服務的業務和技術分離包含兩個方面:
業務內涵和技術結構的分離
通過上面元數據與元結構的概念說明,可以看到服務對外表達是通過一個結構化的樹狀數據結構完成的,這種表達有利于在技術上幫助服務的消費者通過結構化的報文完成服務的調用。而元結構的結構點可以和業務元數據綁定,意味著任何一種結構的數據都可以轉化成僅關注業務要素的服務內涵。
這種分離帶來兩個好處:就是可以在不改變服務內涵的前提下任意的修改服務對外的技術表達形式和結構( 對相同內涵的任意表達 );或者在保證不修改服務對外技術表達的前提下,通過更改業務要素元數據的方法來改變服務的內涵( 對存量表達的任意兼容 )。這種分離給系統帶來巨大的靈活性。
業務內涵與技術接口的分離
為了進一步的完成業務與技術的分離,在S++中將服務的最基本元結構作為服務內涵和缺省外延(缺省對外技術接口)的同時,又進一步的給服務增加更多的獨立接口作為滿足不同需求的技術外延。
在接口中,可以描述不同結構的、經過裁剪和定制的服務要素,并且可以攜帶與服務內涵無關的技術要素。這樣就可以使服務的抽象定義滿足各種不同場景的技術需求,提高系統的靈活性和適應性。
前面舉的只是一個很簡單的例子,其實在服務的接口中可以描述非常豐富的技術信息,包括報文的類型、數據的Format、數據的邊界檢查、與元數據不同的數據類型(方便數據轉換),甚至數據的拆分合合并等等。實際上,接口就是一個從服務實現到服務的一個報文映射和翻譯規則描述文件。
S++的服務多態性
服務的多態性是S++的一個重要特性,也是實現SOA業務敏捷性的一個關鍵。
服務多態性的概念
服務的多態性是指的業務流程在開發態所引用的服務,在運行態會根據業務要素的內容動態的變化為與開發態所定義的服務不同的、但具有一定繼承關系的服務。
舉個例子說明,開發態流程編寫中會調用繳費服務,但是在運行態會根據“待繳費ID”的內容進行判斷,動態的變化為調用諸如繳電話費、繳水費、繳煤氣費等等不同的服務。
服務多態具有以下特征:
-
服務多態必須是運行態提供的能力,開發態只需要關注服務的抽象定義,從而實現業務流程的穩定性。
-
服務多態必須對業務透明。即,在業務流程中不應該出現與運行時動態變化相關的代碼。如果在業務流程中需要判斷一些要素,根據要素由業務來完成服務的動態變化,那么就不是服務多態。
-
服務多態必須支持跨系統的遠程服務調用。當業務流程中需要調用外部服務的時候,服務動態變化的實現必須是自動的,不需要本地業務系統進行支撐。
-
服務多態必須自動的支持業務要素的重載和映射轉換。當業務流程調用的父類抽象服務時,其業務要素可以被子類服務的業務要素重載,例如繳費服務中一個業務要素“待繳費標識號”,繳電話費子類中就會被重載為“待繳費電話號碼”,那么這個重載映射的過程必須由系統來完成,應用應該完全透明。
服務多態性解決的問題域
服務多態性主要為解決系統間相互訪問產生的耦合性問題,由于面向對象方法引入的強耦合性,造成了傳統的系統間調用必須手工的維護業務分支,從而進一步導致業務流程不穩定難以維護。
-
服務多態主要面向系統間相互訪問,系統內更高效的方法還是對象的多態性。
-
服務多態用于解決系統擴展問題,防止業務流程中服務節點的擴展對流程本身造成影響,從而導致需要人工修改流程的問題。
-
服務多態可以屏蔽同類業務分支,例如繳費服務存在繳電話、繳水費等這些種情況;對于不同類型的業務分支無法利用多態性,例如當賬戶余額充足時繳費,不足時返回異常,這樣的分支是無法利用多態性進行簡化的。
-
服務多態不能解決應用架構問題,就如面向對象一樣,在不同應用架構下服務多態的實現方法可能有很大的差異,甚至某些架構下服務多態性不一定能優雅的實現。
S++的應用
解決流程中數據處理問題
S++的出現最初就是要解決流程穩定性問題,所以業務和技術分離第一個主要的應用場景就是消除掉傳統業務流程編排過程中和業務無關的(造成流程不穩定的)環節。舉個例子來說明:
在某流程X中,先后調用服務A和B,那么傳統的任何要在A節點和B節點之間要傳遞的數據,我們會定義一個技術節點,這個技術節點大體上做的事情就是賦值,比如B.field1=A.return1; B.field2=A.return2….
假如未來A和B服務發生了變化,比如增加了字段,那么就需要修改這個賦值節點,增加諸如B.fieldx=A.returnx這樣的賦值映射,這個過程就會造成流程的不穩定。也就是說任何服務的變更都會影響已經做好的流程的改變,這樣的話我們就不能大規模的利用流程編排來開發業務系統,這也是傳統SOA和微服務架構無法避免的問題,這個問題導致了SOA理念無法完整的實施落地。
S++通過元數據隔離了元結構之間的差異性,在服務治理的過程中,通過定義B.fieldx=Metadatax;A.returnx=Metadatax,將元數據Metadatax與具有相同業務要素的元節點綁定,這樣在上述的業務流程中,系統就會自動的動態的生成形如B.field1=A.return1; B.field2=A.return2這樣的代碼,如果服務A和B發生上述變更,編譯器會動態的生成代碼B.fieldx=A.returnx,從而自動的維護了流程的穩定性。
解決服務變更問題(時空穩定性)
我們知道,服務的抽象定義是一個過程,誰也不能保證定義的完美無缺,但是服務的修改往往會帶來所有相關系統的修改,非常麻煩。舉個虛構的例子:
-
我們定義一個取款服務,其中賬號定義為AccountNo,我們為賬號這個元節點綁定一個元數據(假設也叫AccountNo)。
-
我們又定義了一個存款服務,其中賬號也被定義為AccountNo(有可能這個兩個服務不是一個人定義的),并且也被缺省的綁定了同名元數據AccountNo。
-
隨后有個需求(假設叫轉賬服務)需要定義一個流程,這里需要先進行取款操作,然后將取出的款項存入另外一個賬號。那么會為轉賬服務定義一個轉出賬號AccountNo_Out,一個轉入賬號AccountNo_In,顯然這兩個業務要素是不同的,所以分別為他們綁定不同的同名元數據。
-
問題來了,由于當初定義取款和存款服務的時候,沒有考慮到這兩個服務的組合情況,所以如果按上一小節中同名元數據映射的方式,存款和取款賬號作為同一個業務要素就具有相同的數值,顯然就無法完成轉賬業務了。
-
傳統的解決方案是在轉賬流程中增加手工的映射節點,但是這樣顯然就影響了流程的穩定性,而且增加了開發人員的工作量和出錯的概率。但是如果我們去改變服務的結構,比如把取款服務中的AccountNo節點改名為AccountNo_Out,存款服務中的節點改名為AccountNo_In,就會造成現存的服務訪問者需要變更程序,顯然也是不可取的。
-
針對這個問題,S++的解決辦法是保持存、取款服務對外的結構不變,AccountNo這個節點的名稱維持不變,但是將AccountNo所綁定的同名元數據變更為AccountNo_Out和AccountNo_In。這樣,在轉賬服務的流程中系統就會自動的完成正確的數據映射,同時服務對外的接口不發生任何變化,也不會引起存量的服務消費者變更程序。
通過S++的業務與技術分離,可以簡潔的、完全透明的解決服務變更引起的外圍變更,從而達成了服務在時空上的相對穩定性。
解決外圍變更問題(時空穩定性)
S++服務的時空唯一性和穩定性可以通過接口的形式來體現,當服務的外圍系統(包括消費者或提供者)發生變化時,比如升級或更換系統,那么在不修改服務內涵的前提下就可以通過定義新的服務接口來適應外圍變更,從而減小甚至消除變更對整體帶來的影響。
其實,從某種意義上,服務的定義可以看作一種缺省的接口,服務內涵僅關注和定義無結構的、扁平化的元數據作為業務要素。那么不同的接口可以看成用不同方式來定義的服務,用于適應不同的服務消費者和服務提供者,接口就像一個翻譯工具,可以通過接口把不同的語言和方言翻譯成按元數據語言描述的服務。
延伸思考:對真實業務流程的抽象
服務的業務與技術分離以及多態性,帶來一個意想不到的可能性,也就是說我們可以建立與業務和技術都完全無關的 抽象的流程模型 。我也沒有深入思考過的這個問題,下面只是簡單描述一下 抽象流程模型 的概念。
我們知道,做任何稍微復雜的事情一般都會有一定的步驟,我們社會中有很多做事的步驟是相類似的,這是因為長期的社會實踐積累了大量的經驗,這些經驗和知識往往都包含了經過實踐驗證的合理的做事步驟。這種步驟是非常抽象的,可以適用于非常多的不同的社會活動。舉個例子,比如購物這種商業行為,無論你是購買的有型的商品,還是購買無形的服務,我們都可以用一個高度抽象的商業流程來概括完成,如下:
-
首先我們需要一個商品檢索挑選的流程
-
然后我們需要一個維護購物車的流程
-
接下來需要一個下訂單的流程
-
然后是支付的流程
-
然后是物流送貨流程
-
最后是售后流程
事實上,淘寶之類的購物系統已經完成了這種什么都可以賣的需求,但我并不知道他的內部是否是用一種高度抽象的流程來完成所有類似的商業行為的。傳統的業務流程編排工具是無法完成的,因為傳統的流程工具在編排流程的過程中需要關注業務要素,就比如我們前面舉得例子,需要完成B.fieldx=A.returnx這樣的工作,所以這樣的流程就無法滿足各種不同的服務A和服務B的編排(因為A和B的要素會隨著業務的不同而發生變化)。同時,由于傳統的流程引擎無法解決多態問題,所以必須在流程中引用業務服務的具體實現,所以無法運行一個高度抽象的流程。
S++通過業務與技術分離的技術手段,消除了流程引擎對業務要素的依賴,在被編排的流程中只需要引用服務的名稱就可以,不需要關注所調用的服務的要素細節,所以這個抽象的流程就可以運行在任何一個服務系統中,只要能把特定的業務服務與流程中的節點關聯上,就可以由S++系統自動的完成業務要素的匹配工作。同時,用于S++流程中可以調用和具體實現完全無關的抽象服務,這樣就可以不用在開發時刻就決定流程節點的具體服務實現,讓抽象服務在運行時刻再去決定真實的實現。
S++的這種特性,決定了傳統流程工具無法實現的抽象流程模型成為可能。由于本文主要關注S++的特性,所以感興趣的讀者可以自己去研究如何用抽象流程實現跨行業統一的業務流程。
小結
回顧本章,我們從S++的基本概念推導出幾個有價值的推論,再基于這幾個推論解決了我們在面向服務的實踐中遇到的幾個棘手問題,稍微有點兒燒腦…
這幾個推論是:
-
服務具有時空唯一性和穩定性
-
服務無需維護版本
-
在不變更服務的前提下,對同一個服務可以有無數種不同的表達(接口)
-
服務的變更不會影響已經存在的任何一個表達(接口)
基于這幾個推論,我們幸運的發現新增的概念還是能解決一些問題的,大家已經看到:
-
簡化業務流程,穩定開發成果。
-
屏蔽服務變更和外圍變更對系統穩定性的影響,降低系統維護成本。
想通過增加基礎概念來解決實際問題,其實是非常困難的一件事,稍不留神就會被奧姆剃刀無情的剃成禿子,白白的燒腦。
下一章節我們主要探討微服務的業務流程問題,我們所說的業務流程不僅僅是狹義的工作流,而是包含了原本業務系統用硬編碼完成的對象間交互的業務邏輯,同時我們還會通過S++所支撐的算法解決一個效率問題。
作者介紹
李東,14歲開始學習計算機語言,作為課外興趣自學了BASIC和匯編,利用放假期間編寫了貪吃蛇、打飛碟等游戲。高中、大學期間繼續自學軟件編程,曾將C和匯編結合使得從高級語言中能夠調用繪圖功能,并模仿Borland C++開發了一套適合學校機器的圖形化開發環境的原型。
93年大學畢業后在西門子合資公司作為交換機軟件安裝人員工作兩年,然后來到JInfonet公司先后參與4GL的研發和JReport的研發。作為JReport的第一代主要研發人員,編寫了從原型一直到3.0版本的核心引擎部分。2000年與合伙人一起創建了Bi-Soft公司,主營業務是商業智能軟件Bi-Pilot,負責整個產品的研發及管理工作,從最基本的查詢一直到多維分析模型和引擎都是產品的涵蓋范圍。
2007年Bi-Pilot被神州信息收購合并,李東開始在神州信息研發SmartESB產品,用SOA的方法論為客戶提供底層產品服務。