[轉載]微觀SOA:服務設計原則及其實踐方式

     大量互聯網公司都在擁抱SOA和服務化,但業界對SOA的不少討論都比較偏向高大上。本文試圖從稍微不一樣的角度,以相對接地氣的方式來討論SOA,集中討論SOA在微觀實踐層面中的緣起、本質和具體操做方式,另外也用至關篇幅介紹了當今互聯網行業中各類流行的遠程調用技術等等,比較適合從事實際工做的架構師和程序員來閱讀。php

  爲了方便閱讀,本話題將分爲兩篇展示。本文是上篇,着眼於微觀SOA的定義,並簡單分析其核心原則。html

  亞馬遜CEO傑夫·貝佐斯:不爲人知的SOA大師java

  因爲SOA有至關的難度和門檻,不妨先從一個小故事提及,從中能夠管窺一點SOA的大意和做用。程序員

  按照亞馬遜前著名員工Steve Yegge著名的「酒後吐槽」,2002年左右,CEO貝佐斯就在亞馬遜強制推行了如下六個原則(摘自酷殼):web

  1. 全部團隊的程序模塊都要以經過Service Interface 方式將其數據與功能開放出來。
  2. 團隊間的程序模塊的信息通訊,都要經過這些接口。
  3. 除此以外沒有其它的通訊方式。其餘形式一律不容許:不能使用直接鏈結程序、不能直接讀取其餘團隊的數據庫、不能使用共享內存模式、不能使用別人模塊的後門、等等,等等,惟一容許的通訊方式只能是能過調用 Service Interface。
  4. 任何技術均可以使用。好比:HTTP、Corba、Pubsub、自定義的網絡協議、等等,均可以,貝佐斯無論這些。
  5. 全部的Service Interface,毫無例外,都必須從骨子裏到表面上設計成能對外界開放的。也就是說,團隊必須作好規劃與設計,以便將來把接口開放給全世界的程序員,沒有任何例外
  6. 不這樣的作的人會被炒魷魚

  聽說,亞馬遜網站展現一個產品明細的頁面,可能要調用200-300個Service,以便生成高度個性化的內容。正則表達式

  Steve還提到:shell

Amazon已經把文化轉變成了「一切以Service第一」爲系統架構的公司,今天,這已經成爲他們進行全部設計時的基礎,包括那些毫不會被外界所知的僅在內部使用的功能。數據庫

那時,若是沒有被解僱的的恐懼他們必定不會去作。我是說,他們今天仍然怕被解僱,由於這基本上是那兒天天的生活,爲那恐怖的海盜頭子貝佐斯工做。不過,他們這麼作的確是由於他們已經相信Service這就是正確的方向。他們對於SOA的優勢和缺點沒有疑問,某些缺點還很大,也不疑問。但總的來講,這是正確的,由於,SOA驅動出來的設計會產生出平臺(Platform)。編程

  今天,咱們都知道亞馬遜從世界上最大圖書賣場進化爲了世界上最成功的雲平臺……json

  貝佐斯的六原則展現出高度的遠見和超強的信念,即便放到十幾年後的今天,依然以爲振聾發聵……想起一句老話:「不謀萬世者,不足以謀一時;不謀全局者,不足以謀一隅。」

  固然,像貝佐斯這種將神性與魔性集於一身的專橫人物,既可能創造劃時代的進步,也可能製造史無前例的災難。

  SOA漫談:宏觀與微觀

  SOA即面向服務架構,是一個特別大的話題。

  爲了方便討論,我在此先草率的將SOA分爲兩個層面(大概模仿宏觀和微觀經濟學,但這裏的劃分沒有絕對界限):

  • 宏觀SOA:面向高層次的部門級別、公司級別甚至行業級別;涉及商業、管理、技術等方面的綜合的、全局的考慮;架構體系上包括服務治理(governance,如服務註冊,服務監控),服務編排(orchestration,如BPM,ESB),服務協同(choreography,更多面向跨企業集成)等等。我認爲SOA自己最主要是面向宏觀層面的架構,其帶來益處也最能在宏觀高層次上體現出來,同時大部分SOA的業界討論也集中在這方面。
  • 微觀SOA:面向有限的、局部的團隊和我的;涉及獨立的、具體的服務在業務、架構、開發上的考慮。

  不少業界專家都認爲SOA概念過於抽象,不接地氣,我認爲主要是宏觀SOA涉及面太廣,常常須要作通盤考慮,而其中不少方面距離通常人又比較遠。而在微觀層面的SOA更容易達到濤哥過去提出的「三貼近」:貼近實際、貼近生活、貼近羣衆。

  同時,宏觀SOA要取得成功,一般的前提也是SOA在微觀層面的落地與落實,正如宏觀經濟學通常要有堅實的微觀基礎(好比大名鼎鼎的凱恩斯主義曾廣受詬病的一點就是缺少微觀基礎)

  所以,咱們着眼於SOA落地的目的,着重來分析微觀SOA,也算是對業界主流探討的一個小小的補充。

  SOA定義

  按照英文維基百科定義:SOA是一種「軟件」和「軟件架構」的設計模式(或者叫設計原則)。它是基於相互獨立的軟件片斷要將自身的功能經過「服務」提供給其餘應用。

  什麼是「服務」?按照OASIS的定義:Service是一種按照既定「接口「來訪問一個或多個軟件功能的機制(另外這種訪問要符合「服務描述」中策略和限制)

  Service示例(代碼一般以java示例)

public interface Echo {
    String echo(String text);
}
public class EchoImpl implements Echo {
    public String echo(String text) {
        return text;
    }
}

  可能每一個開發人員天天都在寫相似的面向對象的Service,難道這就是在實施SOA嗎?

  SOA設計原則

  既然SOA是設計原則(模式),那麼它包含哪些內容呢?事實上,這方面並無最標準的答案,多數是聽從著名SOA專家Thomas Erl的概括:

標準化的服務契約 Standardized service contract 服務的鬆耦合 Service loose coupling 服務的抽象 Service abstraction 服務的可重用性 Service reusability 服務的自治性 Service autonomy 服務的無狀態性 Service statelessness 服務的可發現性 Service discoverability 服務的可組合性 Service composability ....

  這些原則總的來講要達到的目的是:提升軟件的重用性,減小開發和維護的成本,最終增長一個公司業務的敏捷度。

  可是,業界著名專家如Don Box,David Orchard等人對SOA又有各自不一樣的總結和側重。

  SOA不但沒有絕對統一的原則,並且不少原則自己的內容也具有至關模糊性和寬泛性:例如,所謂鬆耦合原則須要鬆散到什麼程度纔算是符合標準的呢?這就比如一我的要帥到什麼程度纔算是帥哥呢?一棟樓要高到多少米纔算是高樓呢?可能不一樣人心中都有本身的一杆秤……部分因爲這些理論上的不肯定因素,不一樣的人理解或者實施的SOA事實上也可能有比較大的差異。

  淺析鬆耦合原則

  SOA原則比較多,真正的理解每每須要逐步的積累和體會,因此在此不詳細展開。這裏僅以服務的鬆耦合爲例,從不一樣維度來簡單剖析一下這個原則,以說明SOA原則內涵的豐富性:

  • 實現的鬆耦合:這是最基本的鬆耦合,即服務消費端不須要依賴服務契約的某個特定實現,這樣服務提供端的內部變動就不會影響到消費端,並且消費端將來還能夠自由切換到該契約的其餘提供方。

  • 時間的鬆耦合:典型就是異步消息隊列系統,因爲有中介者(broker),因此生產者和消費者沒必要在同一時間都保持可用性以及相同的吞吐量,並且生產者也不須要立刻等到回覆。

  • 位置的鬆耦合:典型就是服務註冊中心和企業服務總線(ESB),消費端徹底不須要直接知道提供端的具體位置,而都經過註冊中心來查找或者服務總線來路由。

  • 版本的鬆耦合:消費端不須要依賴服務契約的某個特定版原本工做,這就要求服務的契約在升級時要儘量的提供向下兼容性。

  SOA與傳統軟件設計

  咱們能夠認爲:SOA ≈ 模塊化開發 + 分佈式計算

  將二者傳統上的最佳實踐結合在一塊兒,基本上能夠推導出SOA的多數設計原則。SOA從軟件設計(暫不考慮業務架構之類)上來說,自身的新東西其實不算不少。

  SOA原則的應用

  基於SOA的原則,也許咱們很難說什麼應用是絕對符合SOA的,可是卻能剔除明顯不符合SOA的應用。

  用上述標準化契約,鬆耦合和可重用這幾個原則來嘗試分析一下上面Echo示例:

  • Echo的服務契約是用Java接口定義,而不是一種與平臺和語言無關的標準化協議,如WSDL,CORBA IDL。固然能夠擡槓,Java也是行業標準,甚至全國牙防組一致認定的東西也是行業標準。

  • Java接口大大加劇了與Service客戶端的耦合度,即要求客戶端必須也是Java,或者JVM上的動態語言(如Groovy、Jython)等等……

  • 同時,Echo是一個Java的本地接口,就要求調用者最好在同一個JVM進程以內……

  • Echo的業務邏輯雖然簡單獨立,但以上技術方面的侷限就致使它沒法之後在其餘場合被輕易重用,好比分佈式環境,異構平臺等等。

  所以,咱們能夠認爲Echo並不太符合SOA的基本設計原則。

  透明化的轉向SOA?

  修改一下上面的Echo,添加Java EE的@WebServices註解(annotation)

@WebServices
public class EchoImpl implements Echo {
    public String echo(String text) {
        return text;
    }
}

  如今將Echo發佈爲Java WebServices,並由底層框架自動生成WSDL來做爲標準化的服務契約,這樣就能與遠程的各類語言和平臺互操做了,較好的解決了上面提到的鬆耦合和可重用的問題。按照通常的理解,Echo彷佛就成爲比較理想的SOA service了。

  可是……即便這個極端簡化的例子,也會引出很多很關鍵的問題,它們決定SOA設計開發的某些難度:

  • 將一個普通的Java對象經過添加註解「透明的」變成WebServices就完成了從面向對象到面向服務的跨越?
  • 經過Java接口生成WSDL服務契約是好的方式嗎?
  • WebServices是最合適遠程訪問技術嗎?

  面向對象和麪向服務的對比

  面向對象(OO)和麪向服務(SO)在基礎理念上有大量共通之處,好比都儘量追求抽象、封裝和低耦合。

  但SO相對於OO,又有很是不一樣的典型應用場景,好比:

  • 多數OO接口(interface)都只被有限的人使用(好比團隊和部門內),而SO接口(或者叫契約)通常來講都不該該對使用者的範圍做出太多的限定和假設(能夠是不一樣部門,不一樣企業,不一樣國家)。還記得貝佐斯原則嗎?「團隊必須作好規劃與設計,以便將來把接口開放給全世界的程序員,沒有任何例外」。

  • 多數OO接口都只在進程內被訪問,而SO接口一般都是被遠程調用。

  簡單講,就是SO接口使用範圍比通常OO接口可能普遍得多。咱們用網站打個比方:一個大型網站的web界面就是它整個系統入口點和邊界,可能要面對全世界的訪問者(因此常常會作國際化之類的工做),而系統內部傳統的OO接口和程序則被隱藏在web界面以後,只被內部較小範圍使用。而理想的SO接口和web界面同樣,也是變成系統入口和邊界,可能要對全世界開發者開放,所以SO在設計開發之中與OO相比其實會有不少不一樣。

  小結

  在前述比較抽象的SOA大原則的基礎上,咱們可嘗試推導一些較細化和可操做的原則,在具體實踐中體現SO的獨特之處。請關注本系列文章的下篇!

==========================================================

     在上一篇文章中,我說到SOA是一個特別大的話題,不但沒有絕對統一的原則,並且不少原則自己的內容也具有至關模糊性和寬泛性。雖然咱們能夠說SOA ≈ 模塊化開發 + 分佈式計算,但因爲其原則的模糊性,咱們仍然很難說什麼應用是絕對符合SOA的,只能識別出哪些是不符合SOA的。

  本篇將對8種可操做的服務設計原則進行細化的分析,做爲SOA實踐的參考。

  服務設計原則1:優化遠程調用

  這裏的遠程調用特指RPC(Remote Procedure Call)。固然更面向對象的說法應該是遠程方法調用或者遠程服務調用等等。

  因爲SO接口一般要被遠程訪問,而網絡傳輸,對象序列化/反序列化等開銷都遠遠超過本地Object訪問幾個數量級,因此要加快系統的響應速度、減小帶寬佔用和提升吞吐量,選擇高性能的遠程調用方式常常是很重要的。

  可是遠程調用方式每每又要受限於具體的業務和部署環境,好比內網、外網、同構平臺、異構平臺等等。有時還要考慮它對諸如分佈式事務,消息級別簽名/加密,可靠異步傳輸等方面的支持程度(這些方面一般被稱爲SLA:service level agreement),甚至還包括開發者的熟悉和接受程度等等。

  所以,遠程調用方式每每須要根據具體狀況作出選擇和權衡。

  以Java遠程Service爲例分析不一樣場景下,傳輸方式的某些可能較好選擇:

  • 內網 + 同框架Java客戶端 + 大併發:多路複用的TCP長鏈接 + kryo (二進制序列化) (kryo也能夠用Protostuff,FST等代替)
  • 內網 + 不一樣框架Java客戶端:TCP + Kryo
  • 內網 + Java客戶端 + 2PC分佈式事務:RMI/IIOP (TCP + 二進制)
  • 內網 + Java客戶端 + 可靠異步調用:JMS + Kryo (TCP + 二進制)
  • 內網 + 不一樣語言客戶端:thrift(TCP + 二進制序列化)
  • 外網 + 不一樣語言客戶端 + 企業級特性:HTTP + WSDL + SOAP (文本)
  • 外網 + 兼顧瀏覽器、手機等客戶端:HTTP + JSON (文本)
  • 外網 + 不一樣語言客戶端 + 高性能:HTTP + ProtocolBuffer (二進制)

  簡單來講,從性能上講,tcp協議 + 二進制序列化更適合內網應用。從兼容性、簡單性上來講,http協議 + 文本序列化更適合外網應用。固然這並非絕對的。另外,tcp協議在這裏並非限定遠程調用協議必定只能是位於OSI網絡模型的第四層的原始tcp,它能夠包含tcp之上的任何非http協議。

  因此,回答上面提到的問題,WebServices (經典的WSDL+SOAP+HTTP)雖然是最符合前述SOA設計原則的技術,但並不等同於SOA,我認爲它只是知足了SOA的底線,而未必是某個具體場景下的最佳選擇。這正如一個十項全能選手在每一個單項上是很難和單項冠軍去競爭的。更理想的SOA Service最好能在能夠支持WebServices的同時,支持多種遠程調用方式,適應不一樣場景,這也是Spring Remoting,SCA,Dubbo,Finagle等分佈式服務框架的設計原則。

  遠程調用技術解釋:HTTP + JSON適合SOA嗎?

  JSON簡單易讀,通用性極佳,甚至能很好支持瀏覽器客戶端,同時也常被手機APP使用,大有取代XML之勢。

  但JSON自己缺少像XML那樣被普遍接受的標準schema,而通常的HTTP + JSON的遠程調用方式也缺少像Thrift,CORBA,WebServices等等那樣標準IDL(接口定義語言),致使服務端和客戶端之間不能造成強的服務契約,也就不能作好比自動代碼生成。因此HTTP + JSON在下降了學習門檻的同時,可能顯著的增長複雜應用的開發工做量和出錯可能性。

  例如,新浪微博提供了基於HTTP + JSON的Open API,但因爲業務操做比較複雜,又在JSON上封裝實現了各類語言的客戶端類庫,來減小用戶的工做量。

  爲了解決這方面的問題,業界有不少不一樣方案來爲HTTP + JSON補充添加IDL,如RSDL、JSON-WSP、WADL、WSDL 2.0等等,但事實上它們的接受度都不太理想。

  另外值得一提的是,JSON格式和XML同樣有冗餘,即便作GZIP壓縮之類的優化,傳輸效率一般也不如不少二進制格式,同時壓縮、解壓還會引入額外的性能開銷。

  遠程調用技術解釋:Apache Thrift多語言服務框架

  Thrift是最初來自facebook的一套跨語言的service開發框架,支持C++, Java, Python, PHP, Ruby, Erlang, Perl, Haskell, C#, JavaScript, Node.js, Smalltalk, Delphi等幾乎全部主流編程語言,具備極好的通用性。

  Thrift被facebook,twitter等巨頭以及開源社區都普遍使用,是很是成熟的技術。

  Thrift的服務契約經過相似以下形式的IDL定義:

struct User {
    1: i32 id,
    2: string name,
    3: string password
}
service UserService {
    void store(1: User user),
    UserProfile retrieve(1: i32 id)
}

  很是相似於C語言,易讀易寫,比WSDL簡單明瞭得多。比用java之類的編程語言也更方便,有時候能夠把全部相關的接口和數據結構定義放到同一個文件,發佈出去的時候不用再打一個壓縮包之類,甚至能夠直接粘貼到文檔中

  Thrift還提供工具,能夠基於IDL自動生成各類語言對應的服務端和客戶端代碼:

[lishen@dangdang thrift]thrift --gen java user.thrift
[lishen@dangdang thrift]$ thrift --gen cpp user.thrift
[lishen@dangdang thrift]$ thrift --gen php user.thrift
[lishen@dangdang thrift]$ thrift --gen csharp user.thrift

  我認爲thrift是比WebServices更簡單高效的技術,是在SOA中對WebServices最具備替代性的技術之一。

  遠程調用技術解釋:多路複用的TCP長鏈接

  這是一種追求極致高性能高伸縮的方式,這裏只作簡要介紹。

  比較典型的是twitter的Mux RPC協議以及google的SPDY協議,在其中多個請求同時共用同一個長鏈接,即一個鏈接交替傳輸不一樣請求的字節塊。它既避免了反覆創建鏈接開銷,也避免了鏈接的等待閒置從而減小了系統鏈接總數,同時還避免了TCP順序傳輸中的線頭阻塞(head-of-line blocking)問題。

  另外,國內比較著名的開源dubbo框架的默認RPC協議,以及業界許多小型開源RPC框架也都是相似的思路。

  採用多路複用機制後,通常就要求服務器端和客戶端都支持額外的相似於會話層(即OSI網絡模型第六層)的語義,致使它們必需要依賴於同一套RPC框架。

  其餘不少RPC機制都是使用TCP短鏈接。即便有些RPC使用了長鏈接,但一個鏈接同一時間只能發送一個請求,而後鏈接就處於閒置狀態,來等待接收該請求的響應,待響應完畢,該鏈接才能被釋放或者複用。

  HTTP 1.1也支持一種基於pipeline模式的長鏈接,其中多個HTTP請求也可共用一個鏈接,但它要求響應(response)也必須按照請求(request)的順序傳輸返回,即FIFO先進先出。而在徹底多路複用的鏈接中,哪一個的響應先ready就能夠先傳輸哪一個,不用排隊。

  固然,短鏈接、長鏈接和多路複用長鏈接之間不存在絕對的好壞,須要取決於具體業務和技術場景,在此不詳細展開了。

  遠程調用技術解釋:Java高效序列化

  最近幾年,各類新的Java高效序列化方式層出不窮,不斷刷新序列化性能的上限,例如Kryo,FST等開源框架。它們提供了很是高效的Java對象的序列化和反序列化實現,相比JDK標準的序列化方式(即基於Serializable接口的標準序列化,暫不考慮用諸如Externalizable接口的定製序列化),在典型場景中,其序列化時間開銷可能縮短20倍以上,生成二進制字節碼的大小可能縮減4倍以上。

  另外,這些高效Java序列化方式的開銷也顯著少於跨語言的序列化方式如thrift的二進制序列化,或者JSON等等

  遠程調用技術解釋:RMI/IIOP和分佈式事務

  RMI/IIOP是Java EE中標準的遠程調用方式,IIOP是CORBA的協議,只有IIOP上的RMI才支持兩階段提交的分佈式事務,同時提供和CORBA的互操做。

  固然,嚴格的兩階段提交事務並不高效,還可能嚴重影響系統伸縮性甚至可用性等等,通常只應用在很是關鍵的業務中。

  遠程調用技術解釋:Google ProtocolBuffer跨語言序列化

  ProtocolBuffer是google開發的跨語言的高效二進制序列化方式,其序列化性能和thrift比較相似。事實上thrift最初就是ProtocolBuffer的仿製品。但它和thrift最大的不一樣是他沒有自帶的RPC實現(由於google沒有將RPC部分開源,但有大量第三方實現)。

  因爲不和RPC方式耦合,反而使得ProtocolBuffer能被方便的集成進大量已有的系統和框架中。在國內它也被百度、淘寶等普遍的應用在Open API中,和HTTP搭配做爲一種高效的跨平臺跨組織的集成方式。

  服務設計原則2:消除冗餘數據

  一樣因爲service的遠程調用開銷很高,因此在它的輸入參數和返回結果中,還要儘可能避免攜帶當前業務用例不須要的冗餘的字段,來減小序列化和傳輸的開銷。同時,去掉冗餘字段也能夠簡化接口,避免給外部用戶帶來沒必要要的業務困惑。

  好比article service中有個返回article list的方法

List<Article> getArticles(...)

  若是業務需求僅僅是要列出文章的標題,那麼在返回的article中就要避免攜帶它的contents等等字段。

  這裏經典解決方案就是引入OO中經常使用的Data Transfer Object (DTO)模式,專門針對特定service的用例來定製要傳輸的數據字段。這裏就是添加一個AriticleSummary的額外數據傳輸對象:

List<ArticleSummary> getArticleSummaries(...)

  額外的DTO確實是個麻煩,而通常OO程序一般則可直接返回本身的包含冗餘的業務模型。

  服務設計原則3:粗粒度契約

  一樣因爲遠程調用開銷高,同時service的外部使用者對特定業務流程的瞭解也比不上組織內部的人,因此service的契約(接口)一般須要是粗粒度的,其中的一個操做就可能對應到一個完整的業務用例或者業務流程,這樣既能減小遠程調用次數,同時又下降學習成本和耦合度。

  而OO接口一般能夠是很是細粒度的,提供最好的靈活性和重用性。

  例如,article service支持批量刪除文章,OO接口中能夠提供

deleteArticle(long id)

  供用戶本身作循環調用(暫不考慮後端SQL之類優化),但SO接口中,則最好提供

deleteArticles(Set<Long> ids)

  供客戶端調用,將可能的N次遠程調用減小爲一次。

  例如,下訂單的用例,要有一系列操做

addItem -> addTax -> calculateTotalPrice -> placeOrder

  OO中咱們徹底可讓用戶本身來靈活選擇,分別調用這些細粒度的可複用的方法。但在SO中,咱們須要將他們封裝到一個粗粒度的方法供用戶作一次性遠程調用,同時也隱藏了內部業務的不少複雜性。另外,客戶端也從依賴4個方法變成了依賴1個方法,從而大大下降了程序耦合度。

  順便值得一提的是,若是上面訂單用例中每一個操做自己也是遠程的service(一般在內網之中),這種粗粒度封裝就變成了經典的service composition(服務組合)甚至service orchestration(服務編排)了。這種狀況下粗粒度service一樣可能提升了性能,由於對外網客戶來講,屢次跨網的遠程調用變成了一次跨網調用 + 屢次內網調用。

  對這種粗粒度service封裝和組合,經典解決方案就是引入OO中經常使用的Facade模式,將原來的對象屏蔽到專門的「外觀」接口以後。同時,這裏也極可能要求咱們引入新的service參數/返回值的數據結構來組合原來多個操做的對象模型,這就一樣用到前述的DTO模式。

  一個簡單Facade示例(FooService和BarService是兩個假想的本地OO service,façade將它們的結果值組合返回):

class FooBarFacadeImpl implements FooBarFacade {
    private FooService fooService;
    private BarService barService;
    public FooBarDto getFooBar() {
        FooBarDto fb = new FooBarDto();
        fb.setFoo(fooService.getFoo());
        fb.setBar(barService.getBar());
        return fb;
    }
}   

  固然,有的時候也能夠不用facade和DTO,而在是FooService和BarService以外添加另外一個本地service和domain model,這要和具體業務場景有關。

  服務設計原則4:通用契約

  因爲service不假設用戶的範圍,因此通常要支持不一樣語言和平臺的客戶端。但各類語言和平臺在功能豐富性上有很大差別,這就決定了服務契約必須取常見語言、平臺以及序列化方式的最大公約數,才能保證service普遍兼容性。由此,服務契約中不能有某些語言才具有的高級特性,參數和返回值也必須是被普遍支持的較簡單的數據類型(好比不能有對象循環引用)。

  若是原有的OO接口不能知足以上要求,則在此咱們一樣須要上述的Facade和DTO,將OO接口轉換爲通用的SO契約。

  例如原有對象模型

class Foo {
   private Pattern regex;
}

  Pattern是Java特有的預編譯好的,可序列化的正則表達式(可提升性能),但在沒有特定框架支持下,可能很差直接被其餘語言識別,因此可添加DTO:

class FooDto {
   private String regex;
}

  服務設計原則5:隔離變化

  雖然OO和SO都追求低耦合,但SO因爲使用者範圍極廣,就要求了更高程度的低耦合性。

  好比前述的article service,OO中能夠直接返回article對象,而這個article對象在OO程序內部可能作爲核心的建模的domain model,甚至做爲O/R mapping等等。而在SO若是還直接返回這個article,即便沒有前面所說的冗餘字段,複雜類型等問題,也可能讓外部用戶與內部系統的核心對象模型,甚至O/R mapping機制,數據表結構等等產生了必定關聯度,這樣一來,內部的重構常常都會可能影響到外部的用戶。

  因此,這裏再次對Facade和DTO產生了需求,用它們做爲中介者和緩衝帶,隔離內外系統,把內部系統變化對外部的衝擊減小到最小程度。

  服務設計原則6:契約先行

  Service是每每涉及不一樣組織之間的合做,而按照正常邏輯,兩個組織之間合做的首要任務,就是先簽定明確的契約,詳細規定雙方合做的內容,合做的形式等等,這樣才能對雙方造成強有力的約束和保障,同時你們的工做也可以並行不悖,不用相互等待。所以SOA中,最佳的實踐方式也是契約先行,即先作契約的設計,能夠有商務,管理和技術等不一樣方面的人員共同參與,並定義出相應的WSDL或者IDL,而後在開發的時候再經過工具自動生成目標語言的對應代碼。

  對於WSDL來講,作契約先行的門檻略高,若是沒有好的XML工具很難手工編制。但對於Thrift IDL或者ProtocolBuffer等來講,因爲它們和普通編程語言相似,因此契約設計相對是比較容易的。另外,對於簡單的HTTP + JSON來講(假設不補充使用其餘描述語言),因爲JSON沒有標準的schema,因此是無法設計具備強約束力的契約的,只能用另外的文檔作描述或者用JSON作輸入輸出的舉例。

  可是,契約先行,而後再生成服務提供端的代碼,畢竟給service開發工做帶來了較大的不便,特別是修改契約的時候致使代碼須要重寫。所以,這裏一樣可能須要引入Facade和DTO,即用契約產生的都是Facade和DTO代碼,它們負責將請求適配和轉發到其餘內部程序,而內部程序則能夠保持本身的主導性和穩定性。

  另外,契約先行可能會給前面提到的多遠程調用支持帶來一些麻煩。

  固然契約先行也許並非能被普遍接受的實踐方式,就像敏捷開發中「測試先行」(也就是測試驅動開發)一般都是最佳實踐,但真正施行的團隊卻很是之少,這方面還須要不斷摸索和總結。但咱們至少能夠認爲Echo中Java2WSDL並不被認爲是SOA的最佳實踐。

  服務設計原則7:穩定和兼容的契約

  因爲用戶範圍的普遍性,因此SO的服務契約和Java標準API相似,在公開發布以後就要保證至關的穩定性,不能隨便被重構,即便升級也要考慮儘量的向下兼容性。同時,若是用契約先行的方式,之後頻繁更改契約也致使開發人員要不斷重作契約到目標語言映射,很是麻煩。

  這就是說SO對契約的質量要求可能大大高於通常的OO接口,理想的狀況下,甚至可能須要專人(包括商務人員)來設計和評估SO契約(無論是否用契約先行的方式),而把內部的程序實現交給不一樣的人,而二者用Facade和DTO作橋樑。

  服務設計原則8:契約包裝

  前述原則基本都是針對service提供端來說的,而對service消費端而言,經過契約生成對應的客戶端代碼,常常就能夠直接使用了。固然,若是契約自己就是Java接口之類(好比在Dubbo,Spring Remoting等框架中),能夠略過代碼生成的步驟。

  可是,service的返回值(DTO)和service接口(Facade),可能被消費端的程序處處引用到。

  這樣消費端程序就較強的耦合在服務契約上了,若是服務契約不是消費端定義的,消費端就等於把本身程序的部分主導權徹底讓渡給了別人。

  一旦契約作更改,或者消費端要選擇徹底不一樣的service提供方(有不一樣的契約),甚至改由本地程序本身來實現相關功能,修改工做量就可能很是大了。

  另外,經過契約生成的客戶端代碼,常常和特定傳輸方式是相關的(好比webservices stub),這樣給切換遠程調用方式也會帶來障礙。

  所以,就像在一般應用中,咱們要包裝數據訪問邏輯(OO中的DAO或者Repository模式),或者包裝基礎服務訪問邏輯(OO中的Gateway模式)同樣,在較理想的SOA設計中,咱們也能夠考慮包裝遠程service訪問邏輯,因爲沒有恰當的名稱,暫時稱之爲Delegate Service模式,它由消費端本身主導定義接口和參數類型,並將調用轉發給真正的service客戶端生成代碼,從而對它的使用者徹底屏蔽了服務契約,這些使用者甚至不知道這個服務究竟是遠程提供的的仍是本地提供的。

  此外,即便咱們在消費端是採用某些手工調用機制(如直接構建和解析json等內容,直接收發JMS消息等等),咱們一樣能夠用delegate service來包裝相應的邏輯。

  delegate service示例1:

// ArticlesService是消費端自定義的接口
class ArticleServiceDelegate implements ArticlesService {
    // 假設是某種自動生成的service客戶端stub類
    private ArticleFacadeStub stub;
    public void deleteArticles(List<Long> ids) {
        stub.deleteArticles(ids);
    }
}   

  delegate service示例2:

// ArticlesService是消費端自定義的接口
class ArticleServiceDelegate implements ArticlesService {
    public void deleteArticles(List<Long> ids) {
        // 用JMS和FastJson手工調用遠程service
        messageClient.sendMessage(queue, JSON.toJSONString(ids));
    }
}   

  從面向對象到面向服務,再從面向服務到面向對象

  總結上面的幾個原則,雖然只是談及有限的幾個方面,但大體也可看出OO和SO在實際的設計開發中仍是有很多顯著的不一樣之處,並且咱們沒有打算用SO的原則來取代過去的OO設計,而是引入額外的層次、對象和OO設計模式,來補充傳統的OO設計。

  其實就是造成了這種調用流程:

  • service提供端:OO程序 <- SOA層(Facade和DTO)<- 遠程消費端

  • service消費端:OO程序 -> Delegate Service -> SOA層(Facade和DTO 或者 其餘動態調用機制)-> 遠程提供端

  Facade、DTO和Delegate Service負責作OO到SO和SO到OO的中間轉換。

  如今,能夠回答Echo示例中的問題:經過「透明的」配置方式,將OO程序發佈爲遠程Service,雖然可能較好的完成了從本地對象到遠程對象的跨越,但一般並不能較好的完成OO到SO的真正跨越。

  同時,透明配置方式也一般沒法直接幫助遺留應用(如ERP等)轉向SOA。

  固然,在較爲簡單和使用範圍肯定頗有限應用(好比傳統和局部的RPC)中,透明式遠程service發佈會帶來極大的便利。

  另外,上面對SO的全部討論都集中在RPC的方式,其實SO中也用message的方式作集成,它也是個大話題,暫時不在此詳論了。

  爲何不能放棄面向對象?

  SO是有它的特定場景的,好比遠程的,範圍不定的客戶端。因此它的那些設計原則並不能被借用來指導通常性的程序開發,好比不少OO程序和SO原則徹底相反,常常都要提供細粒度接口和複雜參數類型以追求使用的使用靈活性和功能的強大性。

  就具體架構而言,我認爲SOA層應該是一個很薄的層次(thin layer),將OO應用或者其餘遺留性應用加以包裝和適配以幫助它們面向服務。其實在一般的web開發中,咱們也是用一個薄的展示層(或者叫Web UI層之類)來包裝OO應用,以幫助它們面向瀏覽器用戶。所以,Façade、DTO等不會取代OO應用中核心的Domain Model、Service等等 (這裏的service是OO中service,未必是SO的)。

  綜合起來,造成相似下面的體系結構:

  理想和現實

  須要特別指出的是,上面提到的諸多SO設計原則是在追求一種相對理想化的設計,以達到架構的優雅性,高效性,可重用性,可維護性,可擴展性等等。

  而在現實中任何理論和原則均可能是須要做出適當妥協的,由於現實是千差萬別的,其狀況遠比理論複雜,很難存在放之四海而皆準的真理。

  並且不少方面彷佛原本也沒有必要追求完美和極致,好比若是有足夠能力擴充硬件基礎設施,就能夠考慮傳輸一些冗餘數據,選擇最簡單傳輸方式,並多來幾回遠程調用等等,以減輕設計開發的工做量。

  那麼理想化的原則就沒有意義了嗎?好比領域驅動設計(Domain-Driven Design)被普遍認爲是最理想的OO設計方式,但極少有項目能徹底採用它;測試驅動開發也被認爲是最佳的敏捷開發方式,但一樣極少有團隊能完全採用它。可是,恐怕沒有多少人在瞭解它們以後會否定它們巨大的意義。

  理想化的原則能夠更好的幫助人們理解某類問題的本質,並作爲好的出發點或者標杆,幫助那些能夠靈活運用,恰當取捨的人取得更大的成績,應付關鍵的挑戰。這正如孔子說的「取乎其上,得乎其中;取乎其中,得乎其下;取乎其下,則無所得矣」。

  另外,值得一提的是,SOA從它的理念自己來講,就帶有一些的理想主義的傾向,好比向「全世界」開放,不限定客戶端等等。若是真願意按SOA的路徑走,即便你是個土豪,偷個懶比浪費網絡帶寬重要,但說不定你的不少用戶是土鱉公司,浪費幾倍的帶寬就大大的影響他們的利潤率。

  延伸討論:SOA和敏捷軟件開發矛盾嗎?

  SOA的服務契約要求至關的穩定性,一旦公開發布(或者雙方合同商定)就不該該有常常的變動,它須要對不少方面有極高的預判。而敏捷軟件開發則是擁抱變化,持續重構的。軟件設計大師Martin Fowler把它們歸結爲計劃式設計和演進式設計的不一樣。

  計劃理論(或者叫建構理論)和演進理論是近代哲學的兩股思潮,影響深遠,派生出了好比計劃經濟和市場經濟,社會主義和自由主義等等各類理論。

  可是,計劃式設計和演進式設計並不絕對矛盾,就像計劃經濟和市場經濟也不絕對矛盾,非此即彼,這方面須要在實踐中不斷摸索。前面咱們討論的設計原則和架構體系,就是將SOA層和OO應用相對隔離,分而治之,在SOA層須要更多計劃式設計,而OO應用能夠相對獨立的演進,從而在必定程度緩解SOA和敏捷開發的矛盾。

  延伸討論:SOA和REST是一回事嗎?

  從最本質的意義上講,REST(Representational State Transfer)實際是一種面向資源架構(ROA),和麪向服務架構(SOA)是有根本區別的。

  例如,REST是基於HTTP協議,對特定資源作增(HTTP POST)、刪(HTTP DELETE)、改(HTTP PUT)、查(HTTP GET)等操做,相似於SQL中針對數據表的INSERT、DELETE、UPDATE、SELECT操做,故REST是以資源(資源能夠類比爲數據)爲中心的。而SOA中的service一般不包含這種針對資源(數據)的細粒度操做,而是面向業務用例、業務流程的粗粒度操做,因此SOA是以業務邏輯爲中心的。

  可是在實際使用中,隨着許多REST基本原則被不斷突破,REST的概念被大大的泛化了,它每每成爲不少基於HTTP的輕量級遠程調用的代名詞(例如前面提到過的HTTP + JSON)。好比,即便是著名的Twitter REST API也違反很多原始REST的基本原則。

  在這個泛化的意義上講,REST也能夠說是有助於實現SOA的一種輕量級遠程調用方式。

  SOA架構的進化

  前面討論的SOA的全部問題,基本都集中在service自己的設計開發。但SOA要真正發揮最大做用,還須要不斷演進成更大的架構(也就是從微觀SOA過渡到宏觀SOA),在此略做說明:

  • 第一個層次是service架構:開發各類獨立的service並知足前面的一些設計原則,咱們前面基本都集中在討論這種架構。這些獨立的service有點相似於小孩的積木。

  • 第二個層次是service composition(組合)架構:獨立的service經過不一樣組合來構成新的業務或者新的service。在理想狀況下,能夠用一種相似小孩搭積木的方式,充分發揮想象力,將獨立的積木(service)靈活的拼裝組合成新的形態,還可以自由的替換其中的某個構件。這體現出SOA高度便捷的重用性,大大提升企業的業務敏捷度。

  • 第三個層次是service inventory(清單)架構:經過標準化企業服務清單(或者叫註冊中心)統一的組織和規劃service的複用和組合。當積木愈來愈多了,若是還滿地亂放而沒有良好的歸類整理,顯然就玩不轉了。

  • 第四個層次是service-oriented enterprise架構……

  總結

  至此,咱們只是簡要的探討了微觀層面的SOA,特別是一些基本設計原則及其實踐方式,以期可以略微展現SOA在實踐中的本質,以有助於SOA更好的落地,進入平常操做層面。

  最後,打個比方:SOA不分貴賤(不分語言、平臺、組織),不遠萬里(經過遠程調用)的提供服務(service),這要求的就是一種全心全意爲人民服務的精神……

 

來源:

http://kb.cnblogs.com/page/505537/

http://www.infoq.com/cn/articles/micro-soa-1

相關文章
相關標籤/搜索