詳解SOA五種基本架構模式
目前,面向服務的架構(SOA)已成為連接復雜服務系統的主要解決方案。雖然SOA的理論很容易理解,但要部署一個設計良好、真正實用的SOA系統卻非常困難。本文試圖通過解析SOA的模式,提供與架構相關的技術指導,進而對以上問題提供詳盡的的解答。
在本文中,一共提到了五種模式。表1列出了這五種模式以及各自相關的問題。
表1:模式列表
其中服務托管(ServiceHost)與主動式服務(Active Service)是兩種最常見的模式——即使服務的使用范圍很小,通常也會使用這兩種模式。這兩種模式的主要內容都與解決服務相關問題有關,即與具體的服務部署有關。
模式一:服務托管
服務托管是我們要討論的第一個模式。它是最基本的模式,或者至少是最基本的模式之一。服務托管模式主要負責運行著服務實例的環境,以及與此相關的路由任務。
問題
隨便選一個服務,任何服務都可以,別告訴我具體是哪個:)。你可以找到一些處理傳入的消息或請求的監聽代碼;你可以找到一些連接組件的代碼,還有一些初始化并激活這個服務的代碼;或許你還能找到一些能適當地配置服務的代碼。有沒有覺得我很厲害?實際上,你可以在服務里找到上面的所有代碼,至少是大部分。
有許多工作都是重復性的、常見的。我們可以好好利用這一點。
如何使服務能夠適應不同的配置,避免設置監聽器、組件連接等重復性常規工作?
第一個辦法(實際上也不是什么辦法),就是為每一個服務重寫所有的連接代碼。很顯然,這不是個好方法,因為重寫的次數越多,就越可能產生一些缺陷。并且,對于維護來說,許多重復的代碼產生的問題更為嚴重。在維護的時候,你不僅要確保每一個服務中的缺陷都已經得到修正,還要保證沒有任何疏漏、所有的服務都已經同步更新。
另一個相對較合理的辦法,就是創建一個共同任務庫,所有的服務都通過API與庫相連接。這樣確實會有所幫助,但是為了充分利用庫的功能,你仍然需要編寫連接代碼。
還有一個辦法是利用繼承創建一個超類,用超類實現共同的功能,然后讓各個服務繼承這個類。然而利用繼承也有問題,因為服務的功能通常無法通過一個單獨的類獲得很好的實現。此外,不同的服務所處理的業務也完全不同——否則它們就是一樣的服務了。因此,也無法讓把這些服務歸于同一類結構。
繼承幾乎已經可以解決我們面臨的問題——因為我們只需要寫一次代碼,只有不同的服務才需要定制。如果想不用繼承得到相同的結果,我們就得使用框架。 解決方案 創建一個通用的服務托管組件,把它當作服務的容器或者框架。容器是可以配置的,并執行服務連接、安裝等工作。
服務托管就像是一個肩負著許多職責的迷你框架。它的第一個職責就是正確地示例服務所包含的組件或類。服務托管還負責讀取配置信息,比如,它可以讀取服務消費者用來連接服務的端口。其它職責包括創建服務環境,比如在終端創建監聽器。最后,服務托管可以負責連接組件——終端監聽器上的協議綁定或者創建一個與數據庫的連接。所有這些職責的共同特征是它們都與服務的實例化和初始化有關,并且正如我們在問題部分所描述的一樣,你很可能會在多個服務中遇到同樣的情況。前面已經提到,服務托管是一個框架,與第二種方法中描述的庫的概念有所不同。庫是一個實用類或方法集,你可以調用它們來獲取特定的功能。而框架則包含了一些功能或流程,并通過調用代碼來擴展流程或將其轉變為具體的流程。這就是“控制反轉(Inversion of Control)”原理。這種原理已經在面向對象的框架中獲得廣泛的應用,比如Spring或Spring.NET、Picocontainers等。
服務托管模式與其它方法相比有許多優勢。其中一個優勢前面已經提到——即服務托管是一種框架,它可以調用代碼來改善性能,而無需你親自進行編排。另一個優勢是它能更好地實現開放/封閉原則(OCP)。OCP原則認為類應該是可以擴展的,但是不能修改;而框架的概念正是這個原則的具體表現。
一個服務托管可以托管多個服務——雖然這種情況可能并不常見。我們曾經構建了一個系統,使用的方案規模小到一臺計算機即可應付。這真的很方便。如果換另一種方案,那么服務可能就要擴展到多臺計算機上,這樣你就得應用多個服務托管實例,各個主機托管服務的一部分,而不是整個服務。
服務托管模式已得到了技術供應商的廣泛應用,這一點我們可以在下面的技術相關部分看到。
模式二:主動式服務
服務的自治性(Autonomous)很重要。自治性能夠提高服務之間的松耦合性,并使整體方案產生更好的靈活性。但是自治性服務有什么實際意義呢?有人說,自治性意味著在不同的服務上工作的團隊的自治性。這種定義表示,由于各個服務之間只有契約上的關系,因此服務之間幾乎沒有依賴性。這意味著各團隊可以獨立工作,專心于自己的服務,而不會互相絆腳。雖然這是一個不錯的“功能”,但是同時還有一個更有價值(比如說商業價值)的定義,就是服務是非常自主(self-sufficient )的。下面我們通過一個示例來解釋這個定義。
問題
有一家報紙訂閱代理機構(比如Ebsco或Blackwell),它需要為客戶創建一份申請。申請服務的一項內容是產生一份形式上的清單。要得到這樣的一份清單,該機構必須同時有給顧客的折扣率和從各出版商處能夠得到的折扣,這樣才能計算出這份申請是否有利潤。 就是這樣一個流程的簡單示意圖。
在場景示例中,申請服務需要等待另外兩個服務的信息。顧客服務是內部服務,與申請服務是同一系統;但出版商的折扣服務卻很可能是外部服務——如果出版商的系統沒有聯機,那么會對我們的申請服務造成什么影響呢?會造成申請服務無法使用。即使我們花費了天文數字的資金來保證申請服務的容錯性,但現在的問題是完全無法使用,因為申請服務是與外部的出版商的服務隨時耦合的。因此申請服務不具有真正的自治性。
如何提高服務的獨立性以及如何處理暫時性的問題?
上面所描述的問題表明,僅僅根據請求喚醒的被動式服務是有問題的,因為服務可能無法滿足依賴于外部服務的契約條件(或服務等級協議)。
一個解決辦法是讓服務對先前的結果進行緩存,但這只能解決部分問題,因為這樣做數據就無法得到及時更新,并且時而也會有緩存失效的情況發生,這時仍然需要連接其它服務。這種方法還有另一個問題,那就是如果傳入的請求過多,在處理一個請求的時候,其它的請求就會處于“等待”的狀態,這樣又會產生資源問題,因為而這些“等待”的請求都需要外部服務的輸入。
即使我們解決了前面的緩存問題,我們仍然得處理其它的暫時性事件。暫時性事件包括重復發生,或者與時間相關的一次性事件。比如,生成每月賬單或發布股票數據或任何其它重復性的報告都是暫時性事件。一種解決方法是從外部編排服務。這種方法的問題是你得將服務的業務邏輯具體化。但是請記住,封閉的服務層是應用SOA的一個重要原因。因此我們得另尋解決方案。
解決方案
要使服務成為主動式服務至少需要一個主動類(Active Class),這個類可以在邊界上,或者服務上,或者兩者都有。然后讓這個主動類處理暫時性問題和管理自治性。 主動服務模式意味著在服務層上執行“主動類”(見圖4)。在Official UML定義中,“主動類”是指“不需要調用方法即可啟動自身行為的對象。”這定義對服務也適用。就是說,服務可以有獨立的線程來處理循環類事件,比如每月賬單或發布狀態。主動式服務也可以監控自身的情況,處理超時,甚至可以用來處理請求
那么,怎么使用主動服務模式來解決我們上面提到的問題呢?就像帕特·森田在《空手道小子》里扮演的宮城先生所說,“最好的防守就是不要在場。”如果你要避免等待另一個服務這種事情發生,那么最好的辦法就是不要等待;你可以主動地、周期性地從其它服務獲取數據,更新你的緩存。你還能給其它服務減少類似的麻煩,并預先發布自己的變化狀態。表面上看起來,緩存數據可能會引起數據重復的問題,但實際上這種情況是不會發生的(詳見下面標注)。
緩存與數據重復問題 我想有些人,特別是那些學過數據庫的人,看到我說從遠程服務主動獲取緩存數據的時候,肯定會從椅子上蹦起來質疑我遠程數據復制的動機,認為我是不是大腦出了什么問題。然而,在我看來,這已經不是相同的數據了。緩存在服務上的數據是服務的數據,可以用來計算、處理甚至根據服務需要進行修改。當然,你也必須明白緩存數據的服務并不是負責控制數據的。
一個帶有計時器的線程基本上足夠應付其它暫時性事件了(如果事件少,你可以為每一個事件安排一個定時器;或者定時喚醒,檢查哪些事件需要處理并處理它們)。
使用邊界組件的線程處理契約相關的暫時性問題是一個好辦法(比如,及時發布狀態、超時等),而服務線程則可以處理純業務類的問題,比如發送每月賬單或者處理傳入的消息隊列。 現在我們看一下如何使用主動服務模式重裝安排如圖的情況。簡單重復一下,如圖是一個申請服務的流程,它需要從外部的發布服務獲取外部數據,并同顧客服務一起為顧客生成一份清單。
現在我們讓申請服務主動地定期獲取折扣信息并緩存結果,這樣當收到產生一份清單的請求時,申請服務就可以即時計算折扣,更快地返回結果,并且(在處理清單請求時)無需依賴外部服務。使用了主動式服務,請求服務便與其它服務分離了。
主動服務模式差不多就是一種理念,沒有太多的技術成分。
模式三:事務處理服務模式
服務構建的另一個重要屬性是:怎么處理從邊界組件或服務中得到的信息?事務處理服務模式(Transactional Service Pattern)可以解決這種問題,并且還能解決可靠性問題。
可以把SOA活動簡化為服務收到服務消費者要求做某件任務的請求,服務處理請求(可能還會請求其它服務一起做這件任務),然后回應發起請求的服務消費者。圖6顯示了這樣的一個商業系統中的活動場景。前臺與訂購服務進行對話。訂購服務登記訂單,把訂單發送到供應商,然后通知賬單服務。這些事件處理完成后,訂購服務向電子商務前臺應用發送一條確認信息。這一切看起來井然有序,但萬一中途發生錯誤怎么辦?
問題
比如說,訂購服務在確認訂單與處理的中途產生故障的話,也就是圖6中的步驟1.1與2.0之間,會出現什么情況呢?我想顧客應該會坐在舒適的沙發上,喝著茶水,等著郵遞員把她訂購的東西送過來。但是雖然她在等,訂單卻已經消失得無影無蹤了。
那么如果服務是在報賬服務處理訂單之間出現故障呢,也就是步驟2.3之前。這種情況下,訂購同樣會消失——除非訂購系統不等待報賬便處理了訂單,這幾乎是不可能的。更糟糕的是,我們已經向供應商發送了訂單,供應商已經把賬單發了過來,并且貨物也隨之送了過來,我們還得給貨物準備庫存。
消息處理過程處處都可能出現前面提到的這些情況。我們可以安慰自己,說大部分情況下系統會正常工作。然而就像莫非定律所說——我們的服務最終必然會在那宗百萬美元的訂單上垮掉。現在的問題是:
如何讓服務可靠地處理請求?
其中一個方案是把這個責任推給服務消費者。在上面提到的場景中,這意味著如果消費者沒有在步驟2.5中收到訂單確認信息,那么這個訂單就是失敗的。然而首先,這個方法并不健全,并會降低服務的自治性——服務無法控制消費者,也無法處理一些其它問題。另外,這只能解決部分問題——那些與服務消費者有關的問題。服務之間的相互作用呢?比如在上面的訂購場景中提到的——即使在步驟2.1向供應商發送訂單以后,仍然可能出現問題。
其中一個方案是把這個責任推給服務消費者。在上面提到的場景中,這意味著如果消費者沒有在步驟2.5中收到訂單確認信息,那么這個訂單就是失敗的。然而首先,這個方法并不健全,并會降低服務的自治性——服務無法控制消費者,也無法處理一些其它問題。另外,這只能解決部分問題——那些與服務消費者有關的問題。服務之間的相互作用呢?比如在上面的訂購場景中提到的——即使在步驟2.1向供應商發送訂單以后,仍然可能出現問題。
第二個辦法是同步處理消息。同步操作在性能上會產生很大的問題,特別是當服務需要與外部服務、系統或資源交互的時候,因為服務在返回結果之前,整個流程都要等待第三方的回應。更主要的是,實際上這并沒有真正解決問題。如果服務在流程中出現故障了,我們無法確認是哪里的故障。我們只知道消息傳輸出現了故障,而要確定故障環節,則需要服務消費者的幫忙。
表面看來,如果服務能夠使用永久性地儲存介質(比如到數據庫)就可以解決這個問題。我覺得這個方向是不錯;但是,這還不夠。因為如果服務在儲存狀態之前出現了故障,傳入的消息仍然會丟失,并且服務對此一無所知。還應該注意到,如果使用永久性存儲介質,我們雖然可以追蹤到在過程的哪一環節出現故障,但是我們無法確定消息是否已經發送到了其它服務。
要解決這些問題,以及整體的可靠性問題,我們需要事務處理服務。
解決方案
使用事務處理服務模式,一次性處理從讀取消息到響應的整個流程。
事務處理服務模式的主要組件是消息泵(message pump),見圖7。消息泵監聽著終端或邊界傳入的消息。如果接收到消息,消息泵就開始一個事務操作,讀取消息,把消息發送到其它組件/類進行處理,處理后,結束事務操作(完成或失敗)。如果可以以事務處理的方式發送請求或回應,就可以把這些操作放入到事務處理中,否則你就需要為操作失敗準備補償邏輯。
使用事務處理編程模型的好處是它要么是純語義學的,要么完全不是,因此不存在邊界效應。由于事務的四個屬性(ACID),所有的操作和消息都一定會被完全處理完、或完全沒有被處理,所以如果有消息離開了服務,那么觸發這個行為的傳入消息肯定是被完全處理過了。 ACID事務
一個事務是一個完整的任務單位。一個任務單位如果滿足ACID所定義的四個屬性,那么它就是一個事務。
* 原子:事務中的所有事件都是以一個原子單位的形式發生的(atomic unit)。這些事件要么全部發生,要么全部不發生。
* 一致:不管事務完成與否,事務的資源必需在整個過程保持一致。 * 隔離:所有外部的觀察者(不參與事務處理)都不能看到內部的狀態。只能在事務處理開始前或完成后查看狀態。
* 持久:事務處理過程中做出的改動儲存在永久性存儲介質中,因此即使系統重啟后也不會丟失。
當然,你要選擇事務服務模式的重要因素肯定還是性能。由于需要準備、為了持久性而做的輸入輸出和鎖定管理等,事務通常會比較慢。我一般會預告確定目標場景并進行測試,以確保能夠得到一個足夠好的方案。
實現事務處理服務模式的一種方法是為所有服務間的消息使用事務消息傳輸。事務消息傳輸 (transactional message transport)使得模式的實現變得非常簡單——只要按照前面提到的步驟來就可以了:開始事務、讀取、處理、發送、完成。另外一種方法,也是更常見的一種方法,是在接收到消息后把消息放到事務處理資源中(比如隊列或數據庫),然后向服務消息者發送一條確認消息。但是這種情況下最初的消息不包括在事務處理中,因此你要準備應付服務消費者的多次消息發送。比如,服務消費者沒有收到確認消息,于是“又”發送了一條請求消息提取100萬美元。
在圖8中,使用事務處理服務模式,步驟2.0到2.5(訂購服務的行為)處于同一事務中。這意味著如果你因為故障或其它意外沒有處理下訂單的消息,那么服務就不會發出任何消息。這是個很讓人開心的消息,因為我們不用再寫復雜的補償邏輯了。這里有一個小問題,那就是如果訂購服務在步驟1.0到1.2之間出現故障的話會有什么情況。該場景不是100%安全的;它有很小的幾率在我們把消息放到隊列等待處理的時候出現故障,從而沒有發送確認消息。這可能導致重復接收到同樣的請求。一個在服務這邊處理重復消息的辦法是在服務啟動時查看消息隊列并對所有消息發送確認消息,這種情況下,服務消費者可能會收到多于一條的確認消息。
請注意,在這個示例中,只能在賬單處理過程僅僅產生一個發貨單的情況下使用單獨的事件。如果賬單服務還需要處理信用卡,并且訂購服務需要得到確認信息才能繼續的話,就不能使用單獨的事件了。當不能使用單獨的事件的時候,需要把過程分成較小的事務,這時整個過程就被稱為連續操作。需要把流程分為幾個較小的事務的另一個條件是看服務是否是分布式的。
必須注意把事務的范圍定到終端/邊界和外部的消息發送者是不一樣的。雖然從表面看來,這個區別不是很重要;但是實際上它確實很重要——因為前者是增強服務的可靠性而后者是提高系統的耦合性并會給你帶來讓人頭痛的問題。如果你把事務擴展到服務之外,那將是非常大的轉變,因為其它的服務是運行在自己的機器的,有它自己的服務等級協議等等。把內部資源暴露到服務信任協議之外是很冒險的做法。
模式四:工作流化模式
我曾經為一家移動公司做過一個項目,構建一個售后服務系統。大家應該都知道,移動公司之間的競爭是非常激烈的。競爭的結果就是,這家公司的銷售部門經常要夜以繼日地工作才能制定出新的使用方案或捆綁銷售計劃以提高銷售額:比如朋友、親情、PTT的公司業務、更低的國際電話費用等方案,3.5G網絡的捆綁推廣等。對于這家公司來說,每周都會有好幾種新式應用方案產生。其記賬系統是基于Amdocs的,SAP系統應付新方案也很輕松。然而,市場競爭通常都是從銷售部門開始的,而不管IT部門的就緒度如何,因此如何盡快地支持新的銷售流程就成了迫切的需求。
幾乎所有企業的業務需求都是不斷變化的——雖然可能不像前面所描述的那般迫切,但它畢竟是存在的。我們必須尋找一種方式讓我們的服務適應這些不斷變化的過程。
問題
如何提高服務對不斷變化的業務流程的適應性?
最容易想到的方法是每次都等待變化的需求,然后根據需求變化開發代碼,更新服務。這里有幾個問題。首先,為了變更需求,你需要一個完整的開發周期。其次,代碼變更意味著系統的很大一部分需要重啟——想一想一些諸如此類的問題吧:“我們昨天的計劃會不會受到這次更新的影響?”;“會對上個周期我們添加的那個類似的東西產生什么影響?”等等。可以說越多的開發和測試就等于越長的上市時間。在我們的項目中,這意味著實施新的計劃需要幾個星期的時間,這會讓管理部門很不高興。這也意味著你的工作評定又降了一級,甚至更多。我們當然不能這么做。
一個較好的辦法是將應用中比較穩定的部分從經常改變的部分中分離出來。比如在我們的方案中,像顧客姓名、地址等人口統計資料應該就是與銷售方案無關的穩定因素。雖然如此,編排穩定的邏輯仍然是一項繁瑣且容易出錯的任務。或許,我們可以想個更好的辦法……
解決方案
在服務中引入一個工作流引擎來處理不穩定的和經常變化的過程、以及編排穩定邏輯(stable logic)。
如圖所示,工作流化模式是在服務中添加一個工作流引擎來驅動業務過程。工作流引擎中包含一個工作流實例(workflow instance)。最基本的形式是每個工作流負責一種請求類型;然而,工作流可以更復雜,處理連續的過程并且有多個接收外部服務請求或數據的入口點。
使用工作流的優勢是可以以活動為構建塊進行思考,從而更靈活、更輕松地安排流程。以活動流的方式建模過程意味著可以更容易地分辨并重用穩定的部分,直到有變化需求為止。既然活動可以進行自我測試,重用一個活動就代表你不用再進行大量的測試。而靈活地重新安排活動則代表你可以迅速地響應業務需求。
這個能夠更容易地(通過工作流)改變服務行為的誘人方案有一個問題:每次行為變更是否需要同時更新契約版本?回答當然是要看情況。我的原則是,對于契約行為來說,如果里氏代換原則成立,那么就不需要添加新的版本。
什么時候更新契約版本——里氏代換原則
里氏代換原則,或契約式設計,是一種面向對象的原則。Barbara Liskoy(里氏)是這樣說的:“如果對于每一個類型S的對象o1都有一個類型T的對象o2,使得以T定義的所有程序P在所有o1都被替換為o2的時候程序P的行為沒有變化,那么S是T的一個子類型。”簡單地說,這就是指子類可以代替父類使用而不會破壞任何使用基類的行為。應用到SOA上這意味著改變服務的內部行為時,如果對于每個契約中定義的操作,前面的情況不變或較弱,而后面的情況(比如請求結果)不變或更強,那么你就不需要創建新的契約版本。換言之,為了保持相同的契約版本,新的服務版本應該與客戶對舊的服務版本的期望行為保持一致。
下面我們把示例的場景工作流化,看看工作流是怎么發揮作用的。簡單重復一下,該場景主要是關于如何更快地為移動公司引入新的使用方案。在引入新的方案的時候,后臺系統通常還沒有就緒——一般需要幾天甚至幾個星期的時間進行改動、測試和部署。而使用工作流的一個優勢就是可以在沒有后臺的人工干預的情況下為新方案提供請求路由支持。比如,我們可以先讓客戶關系管理(CRM)系統記錄某個客戶服務的變更,通知技術人員配置網絡等,然后等后臺系統就緒了,再更新路由把流程指向新系統。此外,正如前面提到的,在這個流程中有許多步驟是穩定的,比如獲取客戶的人口統計數據(姓名、地址等)、為電話提供附加程序或附件等。這些步驟都是可以被幾乎全部銷售過程重用的活動或步驟。在這個場景中添
加一個工作流可以極大地提高業務響應能力并保持業務敏捷性。如果某個競爭對手啟動了一個很受歡迎的新方案,那么這家公司就可以在一天之內回應一個有競爭力的方案。這是真正的有形商業資產。
工作流引擎的另一個優勢是能夠處理持續的過程。它把涉及多信息交互的全部過程直觀地表示出來,使我們更容易對藍圖和過程有一個清楚的了解,因此可以從業務的角度來調試過程。 當然,工作流化也可以與其它模式結合。比如,很容易通過作業調度(幾乎所有的工作流引擎都支持)實現主動式服務模式。
流程編排(Orchestrated Choreography)是一種與工作流化密切相關的模式;這兩種模式都使用相同的底層技術:使用工作流引擎。不過,雖然底層技術一樣,但是不同的架構考慮方式卻會導致選擇不一樣的模式。比如,兩者之間一個很明顯的不同就是工作流化局限于一個單獨的服務中,而流程編排則需要在服務間添加調整性工作流。
模式五:邊界組件
最后一個基本模式是邊界組件模式。稱其它模式為基本模式是因為它們有很大的通用性。但邊界組件模式不同,稱它為基本模式是因為這是一個實現其它模式的平臺。由于邊界組件模式是實現其它模式的一個步驟,具體的示例都是適應于在這個邊界組件上構建的模式的,所以很難想象一個具體的示例來展示它的必要性。不過,我會嘗試通常幾個簡單的例子和這些例子之間的共性來介紹邊界組件。
問題
場景1 我們曾經為一家公司開發了一個海軍C4I平臺(Military Naval C4I platform)。這個平臺有一些可以重用的服務。比如,核心服務之一提供了標準的中央目標視圖。平臺上第一套工具使用了TIBCO Rendezvous消息設施。后來需要更換完全不同的技術(WSE 3.0 )。這兩套工具都使用相同的業務邏輯,但是實現技術不同。
場景2 在另一個項目中(在工作流化模式中提到過),一家移動公司經常需要在一個處理訂單的服務中引入新的應用和銷售方案,比如朋友和親情、晚間話費等。由于詳細變動都是XML相關,因此這個服務接口是非常穩定的,但是業務邏輯卻要為適應新方案而經常變動。 這是一個與場景1截然相反的場景;這里的接口與技術是不變的,而業務邏輯是變動的。
場景3 最后一個場景是許多項目中常見的一種情況。通常系統里會有多個服務。雖然每個服務處理各自不同的業務,但所有這些服務都要執行一些常見的任務,比如在處理請求之前要確認請求是經過驗證的,保存審核條目等等。 在這個場景里,我們遇到了一個不是與單個服務直接相關但在各服務之間重復性卻是最高的功能——因為即使一個服務是處理訂單的,另一個服務是面向顧客的,其記錄請求的代碼都是基本一樣的。 這些場景的共性是每個服務都涉及多個問題(業務邏輯、技術、記錄等)。正如我們所見到的,所有這些問題都必須可以根據情況進行變更而不依賴于其它的問題——我們需要實現這種靈活性。因為我們的問題是:
如何讓服務、技術和其它交叉問題(比如安全、記錄等)等業務方面的問題可以按自己的步調變更而不產生相互的依賴性? 最簡單的(或許過分簡單了)辦法是不要做任何具體的變動。比如,直接把一部分邏輯當作Web服務。這對技術提供商的在線業務來說是很常見的,比如Microsoft (WCF)和 Sun (JAX-WS)提供的教程。然而,由于契約操作與業務邏輯實現直接糾纏在了一起,這給代碼維護帶來了極大的不便——比如,如果要支持場景1,用這種方法來替換技術可能就會非常困難。 我們可以通過在復制服務上替換新技術來解決前面的在當前服務中替換技術的問題,這種方法也叫“自我克隆(own and clone)”。不過這也會產生維護上的問題,因為你現在有了同一業務邏輯的多個復本,因此你得改動所有復本,并且這還解決不了場景3里要在多個服務上添加記錄功能的問題。 如果什么也不做和克隆都行不通,那么我們可能要考慮分開解決各個問題。
解決方案
關注點分離(SoC)在面向對象的設計中是一個為人熟知的概念。其背后的基本原則是單一責任原則(The Single Responsibility Principle),或簡稱為SRP。SRP認為要改變一個類只能有唯一的原因,這個原因就是責任(responsibility)。我們可以在SOA中應用同一原則,把業務邏輯看作是一個責任,把其它的問題看作是另一個責任,這樣我們就得到以下模式: 附加邊界組件(Add Edge Component(s)),用以實現服務并提高靈活性、分離業務邏輯與其它問題(比如契約、協議、終端技術和其它交叉問題)。
正如圖所示,添加邊界組件的主要原因就是關注點分離。邊界組件可以處理所有這些交叉問題以及其它非核心業務問題。這些問題包括負載平衡、格式轉換和審計。這樣,服務的業務邏輯就交給了另一個專門處理業務邏輯的組件。這種分離支持所有前面提到的場景,因為分離可以允許各個部件自由調整。比如,要支持一項新技術(場景1),只需要添加一個邊界組件,但是業務邏輯并不需要更換。如果要改變業務邏輯的行為,就添加一個新的使用方案(場景2),而邊界組件則不需要更換。
從某種意義來說,邊界組件模式可以為SOA提供外觀(fa?ade)、代理、和AOP模式。 我們還要看一下如何解決場景3中服務間的交叉問題。最好的辦法是進一步擴展單一責任原則,并且注意邊界組件實際上是一個組件,不能把它對應于整個類型的類。比如,你可以應用管道和過濾架構類型,把多個類/組件連接到一起,各個類/組件處理特定的問題,以此來創建傳入或傳出的流程。比如,圖12即是一個邊界組件的示例。該示例中,邊界組件提供了一個驗證過濾器來確保消息有正確的格式。然后是一個轉換過濾器把外部契約格式轉為內部格式。最后是一個路由過濾器,負責把消息發送到服務的正確組件。這些組件可以在各個服務中根據需求重用,并且能夠自由地進行更改。
雖然從一開始就在邊界組件和服務之間定義一個內部契約很有吸引力,但是實際上沒有理由這么做,除非你必須支持多個外部契約(雖然實現與消費者一對一的契約非常麻煩——見PTP Integration反模式)。如果服務進展并且創建了新的契約版本,像場景1中像添加新技術,那么在需要支持外部老版本的契約時你可能需要添加內部契約。
-
SOA
+關注
關注
1文章
288瀏覽量
27478
發布評論請先 登錄
相關推薦
評論