2017年09月07日 15:18:10
長相憶Vision 閱讀數(shù):4695 標簽:
架構(gòu) soa 面向服務(wù)的架構(gòu)(SOA)是一個組件模型,它將應(yīng)用程序的不同功能單元(稱為服務(wù))通過這些服務(wù)之間定義良好的接口和契約聯(lián)系起來。接口是采用中立的方式進行定義的,它應(yīng)該獨立于實現(xiàn)服務(wù)的硬件平臺、操作系統(tǒng)和編程語言。這使得構(gòu)建在各種各樣的系統(tǒng)中的服務(wù)可以以一種統(tǒng)一和通用的方式進行交互。
SOA五種基本架構(gòu)模式
目前,面向服務(wù)的架構(gòu)(SOA)已成為連接復(fù)雜服務(wù)系統(tǒng)的主要解決方案。雖然SOA的理論很容易理解,但要部署一個設(shè)計良好、真正實用的SOA系統(tǒng)卻非常困難。本文試圖通過解析SOA的模式,提供與架構(gòu)相關(guān)的技術(shù)指導(dǎo),進而對以上問題提供詳盡的的解答。
在本文中,一共提到了五種模式。表1列出了這五種模式以及各自相關(guān)的問題。
模式名稱相關(guān)問題
服務(wù)托管如何使服務(wù)能夠適應(yīng)不同的配置,避免設(shè)置監(jiān)聽器、組件連接等重復(fù)性常規(guī)工作。
主動式服務(wù)如何提高服務(wù)的獨立性以及如何處理暫時性的問題?
事務(wù)處理服務(wù)如何可靠地處理消息?
工作流化如何提高服務(wù)對不斷變化的業(yè)務(wù)流程的適應(yīng)性?
邊緣組件如何將服務(wù)的業(yè)務(wù)功能從無關(guān)的交叉問題(比如安全、登錄等)中分離出來。
表1:模式列表
其中服務(wù)托管(ServiceHost)與主動式服務(wù)(Active Service)是兩種最常見的模式——即使服務(wù)的使用范圍很小,通常也會使用這兩種模式。這兩種模式的主要內(nèi)容都與解決服務(wù)相關(guān)問題有關(guān),即與具體的服務(wù)部署有關(guān)(見圖1)。
模式一:服務(wù)托管
服務(wù)托管是我們要討論的第一個模式。它是最基本的模式,或者至少是最基本的模式之一。服務(wù)托管模式主要負責(zé)運行著服務(wù)實例的環(huán)境,以及與此相關(guān)的路由任務(wù)。
問題
隨便選一個服務(wù),任何服務(wù)都可以,別告訴我具體是哪個:)。你可以找到一些處理傳入的消息或請求的監(jiān)聽代碼;你可以找到一些連接組件的代碼,還有一些初始化并激活這個服務(wù)的代碼;或許你還能找到一些能適當?shù)嘏渲梅?wù)的代碼。有沒有覺得我很厲害?實際上,你可以在服務(wù)里找到上面的所有代碼,至少是大部分。
有許多工作都是重復(fù)性的、常見的。我們可以好好利用這一點。
如何使服務(wù)能夠適應(yīng)不同的配置,避免設(shè)置監(jiān)聽器、組件連接等重復(fù)性常規(guī)工作?
第一個辦法(實際上也不是什么辦法),就是為每一個服務(wù)重寫所有的連接代碼。很顯然,這不是個好方法,因為重寫的次數(shù)越多,就越可能產(chǎn)生一些缺陷。并且,對于維護來說,許多重復(fù)的代碼產(chǎn)生的問題更為嚴重。在維護的時候,你不僅要確保每一個服務(wù)中的缺陷都已經(jīng)得到修正,還要保證沒有任何疏漏、所有的服務(wù)都已經(jīng)同步更新。
另一個相對較合理的辦法,就是創(chuàng)建一個共同任務(wù)庫,所有的服務(wù)都通過API與庫相連接。這樣確實會有所幫助,但是為了充分利用庫的功能,你仍然需要編寫連接代碼。
還有一個辦法是利用繼承創(chuàng)建一個超類,用超類實現(xiàn)共同的功能,然后讓各個服務(wù)繼承這個類。然而利用繼承也有問題,因為服務(wù)的功能通常無法通過一個單獨的類獲得很好的實現(xiàn)。此外,不同的服務(wù)所處理的業(yè)務(wù)也完全不同——否則它們就是一樣的服務(wù)了。因此,也無法讓把這些服務(wù)歸于同一類結(jié)構(gòu)。
繼承幾乎已經(jīng)可以解決我們面臨的問題——因為我們只需要寫一次代碼,只有不同的服務(wù)才需要定制。如果想不用繼承得到相同的結(jié)果,我們就得使用框架。
解決方案
創(chuàng)建一個通用的服務(wù)托管組件,把它當作服務(wù)的容器或者框架。容器是可以配置的,并執(zhí)行服務(wù)連接、安裝等工作(見圖2)。
服務(wù)托管就像是一個肩負著許多職責(zé)的迷你框架。它的第一個職責(zé)就是正確地示例服務(wù)所包含的組件或類。服務(wù)托管還負責(zé)讀取配置信息,比如,它可以讀取服務(wù)消費者用來連接服務(wù)的端口。其它職責(zé)包括創(chuàng)建服務(wù)環(huán)境,比如在終端創(chuàng)建監(jiān)聽器。最后,服務(wù)托管可以負責(zé)連接組件——終端監(jiān)聽器上的協(xié)議綁定或者創(chuàng)建一個與數(shù)據(jù)庫的連接。所有這些職責(zé)的共同特征是它們都與服務(wù)的實例化和初始化有關(guān),并且正如我們在問題部分所描述的一樣,你很可能會在多個服務(wù)中遇到同樣的情況。前面已經(jīng)提到,服務(wù)托管是一個框架,與第二種方法中描述的庫的概念有所不同。庫是一個實用類或方法集,你可以調(diào)用它們來獲取特定的功能。而框架則包含了一些功能或流程,并通過調(diào)用代碼來擴展流程或?qū)⑵滢D(zhuǎn)變?yōu)榫唧w的流程。這就是“控制反轉(zhuǎn)(Inversion of Control)”原理。這種原理已經(jīng)在面向?qū)ο蟮目蚣苤蝎@得廣泛的應(yīng)用,比如Spring或Spring.NET、Picocontainers等。
服務(wù)托管模式與其它方法相比有許多優(yōu)勢。其中一個優(yōu)勢前面已經(jīng)提到——即服務(wù)托管是一種框架,它可以調(diào)用代碼來改善性能,而無需你親自進行編排。另一個優(yōu)勢是它能更好地實現(xiàn)開放/封閉原則(OCP)。OCP原則認為類應(yīng)該是可以擴展的,但是不能修改;而框架的概念正是這個原則的具體表現(xiàn)。
一個服務(wù)托管可以托管多個服務(wù)——雖然這種情況可能并不常見。我們曾經(jīng)構(gòu)建了一個系統(tǒng),使用的方案規(guī)模小到一臺計算機即可應(yīng)付。這真的很方便。如果換另一種方案,那么服務(wù)可能就要擴展到多臺計算機上,這樣你就得應(yīng)用多個服務(wù)托管實例,各個主機托管服務(wù)的一部分,而不是整個服務(wù)。
服務(wù)托管模式已得到了技術(shù)供應(yīng)商的廣泛應(yīng)用,這一點我們可以在下面的技術(shù)相關(guān)部分看到。
技術(shù)相關(guān)
這一部分我們將簡單地了解一下利用當前的技術(shù)實現(xiàn)這個模式的意義。
服務(wù)托管是一種基本的SOA架構(gòu)模式,因此大部分技術(shù)都支持這種模式。JAX-WS和Windows Communication Foundation都能用標記語言(XML)進行配置,并使用Web服務(wù)器(比如Servlet引擎或IIS)完成大部分的連接工作。
Windows Communication Foundation還提供了一個稱為ServiceHost的類。Microsoft的說明文檔是這樣解釋ServiceHost的:“如果你不使用Internet Information Services (IIS)Windows Activation Services (WAS)提供服務(wù),就用ServiceHost類來配置并提供服務(wù)。IIS和WAS都能與ServiceHost進行交互?!睆母旧险f,其內(nèi)置WCF對ServiceHost的執(zhí)行與我們所描述的服務(wù)托管模式是基本一致的。
如果你要自己實現(xiàn)一個服務(wù)托管模式,那么你可以從Spring(或者Spring.NET)、Picocontainers等輕量級容器中學(xué)習(xí)如何進行連接和實例化。這方面的技術(shù)實現(xiàn)并不多,因為服務(wù)托管模式實際上是一種相對簡單的模式。
輕量級容器與依賴性注射
Spring和其它一些框架被稱為“輕量級容器”。這些“輕量級容器”的優(yōu)點是它們提高了方案的松耦合性和可測試性。這種優(yōu)點是通過一種非SOA模式——依賴性注射模式(Dependency Injection Pattern)實現(xiàn)的。依賴性注射,正如名字所示,指一個類通過第三方“匯編程序”提供所需的接口。通過依賴性注射技術(shù),類不再依賴于特殊的實現(xiàn),而只依賴于接口和抽象類。這使可測試性獲得了提高,因為你可以使用樁或模擬來為類提供虛擬環(huán)境。這同時也提高了靈活性,因為只要它們之間的關(guān)系不變,你就可以輕松地改變具體的實現(xiàn)方式。
服務(wù)托管模式是一種簡單而有效、并且已經(jīng)獲得廣泛應(yīng)用的的模式。
質(zhì)量屬性場景
這一部分是從需求的角度討論使用模式的架構(gòu)效益。大多架構(gòu)需求是根據(jù)使用場景表現(xiàn)的質(zhì)量屬性(可擴展性、靈活性、性能等)來描述的。這些場景也可供其它可以應(yīng)用模式的情況參考。
使用服務(wù)托管模式的主要原因是重用性。由于多個服務(wù)需要使用的相同任務(wù)只需寫一遍代碼,因此重用性也隨之提高。這還能帶來另一個好處,就是可靠性的提高,因為你只需要調(diào)試一次。另外,服務(wù)托管模式還能提供可移植性的質(zhì)量屬性。由于這個模式的分離問題的效應(yīng),因此可移植性也得到了增強。另外由于可以使用標記語言配置服務(wù)環(huán)境,可移植性又會得到進一步提高。
下面的表2列出了幾個場景,可以給你慮使用服務(wù)托管模式的理由。
質(zhì)量屬性(第一層)質(zhì)量屬性(第二層)場景示例
重用性減少開發(fā)時間開發(fā)時,在20分鐘內(nèi)設(shè)定新服務(wù)的環(huán)境。
可移植性安裝系統(tǒng)必須支持以服務(wù)為單位對服務(wù)器進行配置。安裝過程中,從一個環(huán)境切換到另一個環(huán)境所用時間應(yīng)該少于一小時。
一旦配置好并開始運行服務(wù),你就得決定服務(wù)應(yīng)該是被動式的(應(yīng)請求喚醒)或是主動式的(無需等待服務(wù)消費者激活,并做一些零工,比如顯示狀態(tài)或處理超時)。正如名字所示,主動式服務(wù)模式下,服務(wù)是主動運行的,而不是被動的。
模式二:主動式服務(wù)
服務(wù)的自治性(Autonomous)很重要。自治性能夠提高服務(wù)之間的松耦合性,并使整體方案產(chǎn)生更好的靈活性。但是自治性服務(wù)有什么實際意義呢?有人說,自治性意味著在不同的服務(wù)上工作的團隊的自治性。這種定義表示,由于各個服務(wù)之間只有契約上的關(guān)系,因此服務(wù)之間幾乎沒有依賴性。這意味著各團隊可以獨立工作,專心于自己的服務(wù),而不會互相絆腳。雖然這是一個不錯的“功能”,但是同時還有一個更有價值(比如說商業(yè)價值)的定義,就是服務(wù)是非常自主(self-sufficient )的。下面我們通過一個示例來解釋這個定義。
問題
有一家報紙訂閱代理機構(gòu)(比如Ebsco或Blackwell),它需要為客戶創(chuàng)建一份申請。申請服務(wù)的一項內(nèi)容是產(chǎn)生一份形式上的清單。要得到這樣的一份清單,該機構(gòu)必須同時有給顧客的折扣率和從各出版商處能夠得到的折扣,這樣才能計算出這份申請是否有利潤。圖3就是這樣一個流程的簡單示意圖。
在場景示例中,申請服務(wù)需要等待另外兩個服務(wù)的信息。顧客服務(wù)是內(nèi)部服務(wù),與申請服務(wù)是同一系統(tǒng);但出版商的折扣服務(wù)卻很可能是外部服務(wù)——如果出版商的系統(tǒng)沒有聯(lián)機,那么會對我們的申請服務(wù)造成什么影響呢?會造成申請服務(wù)無法使用。即使我們花費了天文數(shù)字的資金來保證申請服務(wù)的容錯性,但現(xiàn)在的問題是完全無法使用,因為申請服務(wù)是與外部的出版商的服務(wù)隨時耦合的。因此申請服務(wù)不具有真正的自治性。
如何提高服務(wù)的獨立性以及如何處理暫時性的問題?
上面所描述的問題表明,僅僅根據(jù)請求喚醒的被動式服務(wù)是有問題的,因為服務(wù)可能無法滿足依賴于外部服務(wù)的契約條件(或服務(wù)等級協(xié)議)。
一個解決辦法是讓服務(wù)對先前的結(jié)果進行緩存,但這只能解決部分問題,因為這樣做數(shù)據(jù)就無法得到及時更新,并且時而也會有緩存失效的情況發(fā)生,這時仍然需要連接其它服務(wù)。這種方法還有另一個問題,那就是如果傳入的請求過多,在處理一個請求的時候,其它的請求就會處于“等待”的狀態(tài),這樣又會產(chǎn)生資源問題,因為而這些“等待”的請求都需要外部服務(wù)的輸入。
即使我們解決了前面的緩存問題,我們?nèi)匀坏锰幚砥渌臅簳r性事件。暫時性事件包括重復(fù)發(fā)生,或者與時間相關(guān)的一次性事件。比如,生成每月賬單或發(fā)布股票數(shù)據(jù)或任何其它重復(fù)性的報告都是暫時性事件。一種解決方法是從外部編排服務(wù)。這種方法的問題是你得將服務(wù)的業(yè)務(wù)邏輯具體化。但是請記住,封閉的服務(wù)層是應(yīng)用SOA的一個重要原因。因此我們得另尋解決方案。
解決方案
要使服務(wù)成為主動式服務(wù)至少需要一個主動類(Active Class),這個類可以在邊界上,或者服務(wù)上,或者兩者都有。然后讓這個主動類處理暫時性問題和管理自治性。
主動服務(wù)模式意味著在服務(wù)層上執(zhí)行“主動類”(見圖4)。在Official UML定義中,“主動類”是指“不需要調(diào)用方法即可啟動自身行為的對象?!边@定義對服務(wù)也適用。就是說,服務(wù)可以有獨立的線程來處理循環(huán)類事件,比如每月賬單或發(fā)布狀態(tài)。主動式服務(wù)也可以監(jiān)控自身的情況,處理超時,甚至可以用來處理請求。
那么,怎么使用主動服務(wù)模式來解決我們上面提到的問題呢?就像帕特·森田在《空手道小子》里扮演的宮城先生所說,“最好的防守就是不要在場。”如果你要避免等待另一個服務(wù)這種事情發(fā)生,那么最好的辦法就是不要等待;你可以主動地、周期性地從其它服務(wù)獲取數(shù)據(jù),更新你的緩存。你還能給其它服務(wù)減少類似的麻煩,并預(yù)先發(fā)布自己的變化狀態(tài)。表面上看起來,緩存數(shù)據(jù)可能會引起數(shù)據(jù)重復(fù)的問題,但實際上這種情況是不會發(fā)生的(詳見下面標注)。
緩存與數(shù)據(jù)重復(fù)問題
我想有些人,特別是那些學(xué)過數(shù)據(jù)庫的人,看到我說從遠程服務(wù)主動獲取緩存數(shù)據(jù)的時候,肯定會從椅子上蹦起來質(zhì)疑我遠程數(shù)據(jù)復(fù)制的動機,認為我是不是大腦出了什么問題。然而,在我看來,這已經(jīng)不是相同的數(shù)據(jù)了。緩存在服務(wù)上的數(shù)據(jù)是服務(wù)的數(shù)據(jù),可以用來計算、處理甚至根據(jù)服務(wù)需要進行修改。當然,你也必須明白緩存數(shù)據(jù)的服務(wù)并不是負責(zé)控制數(shù)據(jù)的。
一個帶有計時器的線程基本上足夠應(yīng)付其它暫時性事件了(如果事件少,你可以為每一個事件安排一個定時器;或者定時喚醒,檢查哪些事件需要處理并處理它們)。
使用邊界組件的線程處理契約相關(guān)的暫時性問題是一個好辦法(比如,及時發(fā)布狀態(tài)、超時等),而服務(wù)線程則可以處理純業(yè)務(wù)類的問題,比如發(fā)送每月賬單或者處理傳入的消息隊列。
現(xiàn)在我們看一下如何使用主動服務(wù)模式重裝安排圖3的情況。簡單重復(fù)一下,圖3是一個申請服務(wù)的流程,它需要從外部的發(fā)布服務(wù)獲取外部數(shù)據(jù),并同顧客服務(wù)一起為顧客生成一份清單(見圖5)。現(xiàn)在我們讓申請服務(wù)主動地定期獲取折扣信息并緩存結(jié)果,這樣當收到產(chǎn)生一份清單的請求時,申請服務(wù)就可以即時計算折扣,更快地返回結(jié)果,并且(在處理清單請求時)無需依賴外部服務(wù)。使用了主動式服務(wù),請求服務(wù)便與其它服務(wù)分離了。
主動服務(wù)模式差不多就是一種理念,沒有太多的技術(shù)成分。
技術(shù)相關(guān)
這一部分討論如何使用SOA相關(guān)技術(shù)實現(xiàn)這個模式;然而,因為這里并沒有太多的技術(shù)成分,因此我們也只是簡單的說明一下。
主動服務(wù)模式的技術(shù)理念就是在有特殊功能的服務(wù)或邊界組件上使用主動線程。從本質(zhì)上說,主動服務(wù)模式取決于線程技術(shù)。你可以用任何語言實現(xiàn)這種線程技術(shù)。關(guān)鍵在于,你要用這個線程做什么。在上面的段落里我們用了從其它服務(wù)緩存數(shù)據(jù)并處理重復(fù)性報告的例子。
下面的問題就是什么時候應(yīng)該使用主動服務(wù)模式?我們來看一下動機。
質(zhì)量屬性場景
這一部分是從需求的角度討論使用模式的架構(gòu)效益。大多架構(gòu)需求是根據(jù)使用場景表現(xiàn)的質(zhì)量屬性(可擴展性、靈活性、性能等)來描述的。這些場景也可供其它可以應(yīng)用模式的情況參考。
主動服務(wù)是一些其它模式(比如前面提到的分離調(diào)用和blogjecting watchdog)的先決條件,而這些模式可以幫助處理質(zhì)量屬性問題,比如可靠性與可用性。并且,即使是單獨使用主動服務(wù)模式也能滿足許多質(zhì)量屬性要求。
通過預(yù)先準備的數(shù)據(jù),主動式服務(wù)可以減少一些潛在的問題。它可以解決期限的問題,因為它能保證服務(wù)在期限之前完成任務(wù)(比如按時生成每月賬單)。另外,主動服務(wù)模式還有可用性的優(yōu)勢,因為從其它服務(wù)查詢并緩存數(shù)據(jù)意味著減少了服務(wù)的可用性對其它服務(wù)的依賴性。
下面的表3列出了一些主動服務(wù)模式可以發(fā)揮作用的場景。
質(zhì)量屬性(第一層)質(zhì)量屬性(第二層)場景示例
性能延遲正常情況下,評估申請的效益只需要2秒不到的時間
性能期限未滿負載的正常情況下,系統(tǒng)可以每少刷新兩次以上股票價格
可用性正常運行時間即使斷開遠程連接,用戶仍然可以生成報價單
模式三:事務(wù)處理服務(wù)模式
服務(wù)構(gòu)建的另一個重要屬性是:怎么處理從邊界組件或服務(wù)中得到的信息?事務(wù)處理服務(wù)模式(Transactional Service Pattern)可以解決這種問題,并且還能解決可靠性問題。
可以把SOA活動簡化為服務(wù)收到服務(wù)消費者要求做某件任務(wù)的請求,服務(wù)處理請求(可能還會請求其它服務(wù)一起做這件任務(wù)),然后回應(yīng)發(fā)起請求的服務(wù)消費者。圖6顯示了這樣的一個商業(yè)系統(tǒng)中的活動場景。前臺與訂購服務(wù)進行對話。訂購服務(wù)登記訂單,把訂單發(fā)送到供應(yīng)商,然后通知賬單服務(wù)。這些事件處理完成后,訂購服務(wù)向電子商務(wù)前臺應(yīng)用發(fā)送一條確認信息。這一切看起來井然有序,但萬一中途發(fā)生錯誤怎么辦?
問題
比如說,訂購服務(wù)在確認訂單與處理的中途產(chǎn)生故障的話,也就是圖6中的步驟1.1與2.0之間,會出現(xiàn)什么情況呢?我想顧客應(yīng)該會坐在舒適的沙發(fā)上,喝著茶水,等著郵遞員把她訂購的東西送過來。但是雖然她在等,訂單卻已經(jīng)消失得無影無蹤了。
那么如果服務(wù)是在報賬服務(wù)處理訂單之間出現(xiàn)故障呢,也就是步驟2.3之前。這種情況下,訂購?fù)瑯訒А怯嗁徬到y(tǒng)不等待報賬便處理了訂單,這幾乎是不可能的。更糟糕的是,我們已經(jīng)向供應(yīng)商發(fā)送了訂單,供應(yīng)商已經(jīng)把賬單發(fā)了過來,并且貨物也隨之送了過來,我們還得給貨物準備庫存。
消息處理過程處處都可能出現(xiàn)前面提到的這些情況。我們可以安慰自己,說大部分情況下系統(tǒng)會正常工作。然而就像莫非定律所說——我們的服務(wù)最終必然會在那宗百萬美元的訂單上垮掉?,F(xiàn)在的問題是:
如何讓服務(wù)可靠地處理請求?
其中一個方案是把這個責(zé)任推給服務(wù)消費者。在上面提到的場景中,這意味著如果消費者沒有在步驟2.5中收到訂單確認信息,那么這個訂單就是失敗的。然而首先,這個方法并不健全,并會降低服務(wù)的自治性——服務(wù)無法控制消費者,也無法處理一些其它問題。另外,這只能解決部分問題——那些與服務(wù)消費者有關(guān)的問題。服務(wù)之間的相互作用呢?比如在上面的訂購場景中提到的——即使在步驟2.1向供應(yīng)商發(fā)送訂單以后,仍然可能出現(xiàn)問題。
第二個辦法是同步處理消息。同步操作在性能上會產(chǎn)生很大的問題,特別是當服務(wù)需要與外部服務(wù)、系統(tǒng)或資源交互的時候,因為服務(wù)在返回結(jié)果之前,整個流程都要等待第三方的回應(yīng)。更主要的是,實際上這并沒有真正解決問題。如果服務(wù)在流程中出現(xiàn)故障了,我們無法確認是哪里的故障。我們只知道消息傳輸出現(xiàn)了故障,而要確定故障環(huán)節(jié),則需要服務(wù)消費者的幫忙。
表面看來,如果服務(wù)能夠使用永久性地儲存介質(zhì)(比如到數(shù)據(jù)庫)就可以解決這個問題。我覺得這個方向是不錯;但是,這還不夠。因為如果服務(wù)在儲存狀態(tài)之前出現(xiàn)了故障,傳入的消息仍然會丟失,并且服務(wù)對此一無所知。還應(yīng)該注意到,如果使用永久性存儲介質(zhì),我們雖然可以追蹤到在過程的哪一環(huán)節(jié)出現(xiàn)故障,但是我們無法確定消息是否已經(jīng)發(fā)送到了其它服務(wù)。
要解決這些問題,以及整體的可靠性問題,我們需要事務(wù)處理服務(wù)。
解決方案
使用事務(wù)處理服務(wù)模式,一次性處理從讀取消息到響應(yīng)的整個流程。
事務(wù)處理服務(wù)模式的主要組件是消息泵(message pump),見圖7。消息泵監(jiān)聽著終端或邊界傳入的消息。如果接收到消息,消息泵就開始一個事務(wù)操作,讀取消息,把消息發(fā)送到其它組件/類進行處理,處理后,結(jié)束事務(wù)操作(完成或失?。H绻梢砸允聞?wù)處理的方式發(fā)送請求或回應(yīng),就可以把這些操作放入到事務(wù)處理中,否則你就需要為操作失敗準備補償邏輯。
使用事務(wù)處理編程模型的好處是它要么是純語義學(xué)的,要么完全不是,因此不存在邊界效應(yīng)。由于事務(wù)的四個屬性(ACID),所有的操作和消息都一定會被完全處理完、或完全沒有被處理,所以如果有消息離開了服務(wù),那么觸發(fā)這個行為的傳入消息肯定是被完全處理過了。
ACID事務(wù)
一個事務(wù)是一個完整的任務(wù)單位。一個任務(wù)單位如果滿足ACID所定義的四個屬性,那么它就是一個事務(wù)。
* 原子:事務(wù)中的所有事件都是以一個原子單位的形式發(fā)生的(atomic unit)。這些事件要么全部發(fā)生,要么全部不發(fā)生。
* 一致:不管事務(wù)完成與否,事務(wù)的資源必需在整個過程保持一致。
* 隔離:所有外部的觀察者(不參與事務(wù)處理)都不能看到內(nèi)部的狀態(tài)。只能在事務(wù)處理開始前或完成后查看狀態(tài)。
* 持久:事務(wù)處理過程中做出的改動儲存在永久性存儲介質(zhì)中,因此即使系統(tǒng)重啟后也不會丟失。
當然,你要選擇事務(wù)服務(wù)模式的重要因素肯定還是性能。由于需要準備、為了持久性而做的輸入輸出和鎖定管理等,事務(wù)通常會比較慢。我一般會預(yù)告確定目標場景并進行測試,以確保能夠得到一個足夠好的方案。
實現(xiàn)事務(wù)處理服務(wù)模式的一種方法是為所有服務(wù)間的消息使用事務(wù)消息傳輸。事務(wù)消息傳輸(transactional message transport)使得模式的實現(xiàn)變得非常簡單——只要按照前面提到的步驟來就可以了:開始事務(wù)、讀取、處理、發(fā)送、完成。另外一種方法,也是更常見的一種方法,是在接收到消息后把消息放到事務(wù)處理資源中(比如隊列或數(shù)據(jù)庫),然后向服務(wù)消息者發(fā)送一條確認消息。但是這種情況下最初的消息不包括在事務(wù)處理中,因此你要準備應(yīng)付服務(wù)消費者的多次消息發(fā)送。比如,服務(wù)消費者沒有收到確認消息,于是“又”發(fā)送了一條請求消息提取100萬美元。
圖8是圖6的事務(wù)處理服務(wù)模式。簡單重復(fù)一下,該場景是一個關(guān)于電子商務(wù)的前臺訂購服務(wù)。訂購服務(wù)登記訂單,把訂單發(fā)送到供應(yīng)商,然后通知賬單服務(wù)。這些事件處理完成后,訂購服務(wù)向電子商務(wù)前臺應(yīng)用發(fā)送一條確認信息。
在圖8中,使用事務(wù)處理服務(wù)模式,步驟2.0到2.5(訂購服務(wù)的行為)處于同一事務(wù)中。這意味著如果你因為故障或其它意外沒有處理下訂單的消息,那么服務(wù)就不會發(fā)出任何消息。這是個很讓人開心的消息,因為我們不用再寫復(fù)雜的補償邏輯了。這里有一個小問題,那就是如果訂購服務(wù)在步驟1.0到1.2之間出現(xiàn)故障的話會有什么情況。該場景不是100%安全的;它有很小的幾率在我們把消息放到隊列等待處理的時候出現(xiàn)故障,從而沒有發(fā)送確認消息。這可能導(dǎo)致重復(fù)接收到同樣的請求。一個在服務(wù)這邊處理重復(fù)消息的辦法是在服務(wù)啟動時查看消息隊列并對所有消息發(fā)送確認消息,這種情況下,服務(wù)消費者可能會收到多于一條的確認消息。
請注意,在這個示例中,只能在賬單處理過程僅僅產(chǎn)生一個發(fā)貨單的情況下使用單獨的事件。如果賬單服務(wù)還需要處理信用卡,并且訂購服務(wù)需要得到確認信息才能繼續(xù)的話,就不能使用單獨的事件了。當不能使用單獨的事件的時候,需要把過程分成較小的事務(wù),這時整個過程就被稱為連續(xù)操作。需要把流程分為幾個較小的事務(wù)的另一個條件是看服務(wù)是否是分布式的。
必須注意把事務(wù)的范圍定到終端/邊界和外部的消息發(fā)送者是不一樣的。雖然從表面看來,這個區(qū)別不是很重要;但是實際上它確實很重要——因為前者是增強服務(wù)的可靠性而后者是提高系統(tǒng)的耦合性并會給你帶來讓人頭痛的問題。如果你把事務(wù)擴展到服務(wù)之外,那將是非常大的轉(zhuǎn)變,因為其它的服務(wù)是運行在自己的機器的,有它自己的服務(wù)等級協(xié)議等等。把內(nèi)部資源暴露到服務(wù)信任協(xié)議之外是很冒險的做法。
現(xiàn)在我們看看如何實現(xiàn)事務(wù)處理服務(wù)。
技術(shù)相關(guān)
這一部分我們將簡單的了解一下利用當前的技術(shù)實現(xiàn)這個模式的意義。
如果消息傳輸是對事務(wù)敏感的,那么實現(xiàn)事務(wù)處理服務(wù)就容易得多。大部分ESB(比如Sonic ESB和Iona Artix)都是事務(wù)敏感的,另外還有消息中間件(比如MSMQ)、所有JMS實現(xiàn)以及SQL Server Service Broker。如果你在使用事務(wù)消息傳輸,你可以通過僅僅在資源上創(chuàng)建一個事務(wù)來實現(xiàn)事務(wù)處理服務(wù)模式。如果,比如你還要在同一任務(wù)單元中進行更新數(shù)據(jù)庫的操作,你可能還需要分布式的事務(wù)處理。然后只要從ESB或消息中間件讀取消息、處理、發(fā)送響應(yīng)或其它處理過程生成的消息到外部或目標隊列,最后一切順利的話結(jié)束事務(wù)。
要注意通常要在事務(wù)處理中用到多項資源,比如,一個消息隊列和一個用于存儲消息處理后的任何狀態(tài)變化的數(shù)據(jù)庫,這時你應(yīng)該使用分布式事務(wù)處理。在.NET 2.0及以上版本中,如果有需要的話,你可以通過打開一個TransactionScope對象(定義于System.Transactions)顯式地過渡到分布式事務(wù)處理。
如果消息傳輸不支持事務(wù),只在你把消息存儲到了事務(wù)存儲庫(比如隊列或表)后有確認信息。你仍然有在沒有得到服務(wù)消費者的確認的情況下處理事務(wù)的風(fēng)險,因此你得做好再次接收請求的準備,以防確認消息丟失或根本就沒有發(fā)送。如果出現(xiàn)故障,而處理傳入的消息的事務(wù)向其它服務(wù)發(fā)送了消息,你也要準備好補償邏輯。
毒信息
當我們以事務(wù)的方式讀取信息時,需要注意分辨并處理毒信息。毒信息是一種有缺陷的信息,它會使服務(wù)發(fā)生故障,或者使服務(wù)在處理這條消息的時候一直產(chǎn)生處理失敗的結(jié)果。原因在于,如果在事務(wù)中讀取了一條毒信息,所產(chǎn)生的故障就會使信息返回隊列;而毒信息則在隊列中等待服務(wù)恢復(fù),然后再被服務(wù)讀取,依此循環(huán)。某些信息技術(shù)產(chǎn)品可以辯認這種毒信息并將其丟棄。你需要確認的是這些辯論機制到位,并且能夠妥善地處理所有故障,或者至少把這些故障交給你親自處理。
有一種叫WS-ReliableMessaging貌似跟此有關(guān)。然而,雖然名字看起來很相似,但是實際上這只是一種能夠穩(wěn)定地點對點傳輸消息的協(xié)議,可以說更像是HTTP的TCP協(xié)議,跟持久性或事務(wù)處理根本沒有一點關(guān)系。但許多ESB是事務(wù)性質(zhì)的,能夠支持這種協(xié)議,因此你可以通過結(jié)合標準協(xié)議和事務(wù)消息處理來得到一個完善的結(jié)果。
其它相關(guān)的協(xié)議有WS-Coordination和其“同宗”的WS-AtomicTransaction和WS-BusinessActivity?,F(xiàn)在我們主要來談?wù)刉S-AtomicTransaction。從根本上說,WS-AtomicTransaction定義了一種編排分布式事務(wù)處理的協(xié)議。一般來說,我不會建議使用WS-AtomicTransaction,因為它在服務(wù)間引入了太多的耦合關(guān)系。比如,在圖8描述的場景里——我們真的想在等待外部供應(yīng)商答復(fù)的時候鎖定資源并且降低我們的訂單的優(yōu)先級嗎?
如果使用了事務(wù)敏感中間件的話,情況就會有所不同。這時已經(jīng)不是跨越服務(wù)的單一事務(wù),而是分成了三個更小的事務(wù):一個發(fā)送服務(wù);內(nèi)部中間件保障消息的傳遞;最后一個在中間件與讀取者之間——這是與基礎(chǔ)設(shè)施的耦合,可以從邊界組件上分離出來。
質(zhì)量屬性場景
這一部分是從需求的角度討論使用模式的架構(gòu)效益。大多架構(gòu)需求是通過使用場景表現(xiàn)的質(zhì)量屬性(可擴展性、靈活性、性能等)來描述的。這些場景也可供其它情況作為應(yīng)用模式的參考。
事務(wù)處理服務(wù)引入的事務(wù)語義可以簡化編碼和測試。另外,它還能極大地提高服務(wù)的可靠性與穩(wěn)定性。因為其“全有或全無”的屬性使得編碼變得簡單,這讓開發(fā)人員能夠集中精力實現(xiàn)商業(yè)價值,而不是考慮一些邊界效應(yīng)或者類似的問題。
下面是幾個場景,可以給你考慮使用服務(wù)托管模式的理由。
質(zhì)量屬性(第一層)質(zhì)量屬性(第二層)場景示例
可靠性數(shù)據(jù)損失在任何情況下,凡是經(jīng)過系統(tǒng)確認的消息都不會丟失
易測性覆蓋率所有場景都能得到95%甚至更高的測試覆蓋率
之所以事務(wù)處理服務(wù)模式能為我們節(jié)省編碼時間,是因為事務(wù)不像非事務(wù)代碼有那么多的邊界效應(yīng)。還有一種可以節(jié)省編寫代碼時間的模式是工作流化模式(Workflodize Pattern)。
模式四:工作流化模式
我曾經(jīng)為一家移動公司做過一個項目,構(gòu)建一個售后服務(wù)系統(tǒng)。大家應(yīng)該都知道,移動公司之間的競爭是非常激烈的。競爭的結(jié)果就是,這家公司的銷售部門經(jīng)常要夜以繼日地工作才能制定出新的使用方案或捆綁銷售計劃以提高銷售額:比如朋友、親情、PTT的公司業(yè)務(wù)、更低的國際電話費用等方案,3.5G網(wǎng)絡(luò)的捆綁推廣等。對于這家公司來說,每周都會有好幾種新式應(yīng)用方案產(chǎn)生。其記賬系統(tǒng)是基于Amdocs的,SAP系統(tǒng)應(yīng)付新方案也很輕松。然而,市場競爭通常都是從銷售部門開始的,而不管IT部門的就緒度如何,因此如何盡快地支持新的銷售流程就成了迫切的需求。
幾乎所有企業(yè)的業(yè)務(wù)需求都是不斷變化的——雖然可能不像前面所描述的那般迫切,但它畢竟是存在的。我們必須尋找一種方式讓我們的服務(wù)適應(yīng)這些不斷變化的過程。
問題
如何提高服務(wù)對不斷變化的業(yè)務(wù)流程的適應(yīng)性?
最容易想到的方法是每次都等待變化的需求,然后根據(jù)需求變化開發(fā)代碼,更新服務(wù)。這里有幾個問題。首先,為了變更需求,你需要一個完整的開發(fā)周期。其次,代碼變更意味著系統(tǒng)的很大一部分需要重啟——想一想一些諸如此類的問題吧:“我們昨天的計劃會不會受到這次更新的影響?”;“會對上個周期我們添加的那個類似的東西產(chǎn)生什么影響?”等等??梢哉f越多的開發(fā)和測試就等于越長的上市時間。在我們的項目中,這意味著實施新的計劃需要幾個星期的時間,這會讓管理部門很不高興。這也意味著你的工作評定又降了一級,甚至更多。我們當然不能這么做。
一個較好的辦法是將應(yīng)用中比較穩(wěn)定的部分從經(jīng)常改變的部分中分離出來。比如在我們的方案中,像顧客姓名、地址等人口統(tǒng)計資料應(yīng)該就是與銷售方案無關(guān)的穩(wěn)定因素。雖然如此,編排穩(wěn)定的邏輯仍然是一項繁瑣且容易出錯的任務(wù)?;蛟S,我們可以想個更好的辦法……
解決方案
在服務(wù)中引入一個工作流引擎來處理不穩(wěn)定的和經(jīng)常變化的過程、以及編排穩(wěn)定邏輯(stable logic)。
如圖9所示,工作流化模式是在服務(wù)中添加一個工作流引擎來驅(qū)動業(yè)務(wù)過程。工作流引擎中包含一個工作流實例(workflow instance)。最基本的形式是每個工作流負責(zé)一種請求類型;然而,工作流可以更復(fù)雜,處理連續(xù)的過程并且有多個接收外部服務(wù)請求或數(shù)據(jù)的入口點。
使用工作流的優(yōu)勢是可以以活動為構(gòu)建塊進行思考,從而更靈活、更輕松地安排流程。以活動流的方式建模過程意味著可以更容易地分辨并重用穩(wěn)定的部分,直到有變化需求為止。既然活動可以進行自我測試,重用一個活動就代表你不用再進行大量的測試。而靈活地重新安排活動則代表你可以迅速地響應(yīng)業(yè)務(wù)需求。
這個能夠更容易地(通過工作流)改變服務(wù)行為的誘人方案有一個問題:每次行為變更是否需要同時更新契約版本?回答當然是要看情況。我的原則是,對于契約行為來說,如果里氏代換原則成立,那么就不需要添加新的版本。
什么時候更新契約版本——里氏代換原則
里氏代換原則,或契約式設(shè)計,是一種面向?qū)ο蟮脑瓌t。Barbara Liskoy(里氏)是這樣說的:“如果對于每一個類型S的對象o1都有一個類型T的對象o2,使得以T定義的所有程序P在所有o1都被替換為o2的時候程序P的行為沒有變化,那么S是T的一個子類型?!焙唵蔚卣f,這就是指子類可以代替父類使用而不會破壞任何使用基類的行為。應(yīng)用到SOA上這意味著改變服務(wù)的內(nèi)部行為時,如果對于每個契約中定義的操作,前面的情況不變或較弱,而后面的情況(比如請求結(jié)果)不變或更強,那么你就不需要創(chuàng)建新的契約版本。換言之,為了保持相同的契約版本,新的服務(wù)版本應(yīng)該與客戶對舊的服務(wù)版本的期望行為保持一致。
下面我們把示例的場景工作流化,看看工作流是怎么發(fā)揮作用的。簡單重復(fù)一下,該場景主要是關(guān)于如何更快地為移動公司引入新的使用方案。在引入新的方案的時候,后臺系統(tǒng)通常還沒有就緒——一般需要幾天甚至幾個星期的時間進行改動、測試和部署。而使用工作流的一個優(yōu)勢就是可以在沒有后臺的人工干預(yù)的情況下為新方案提供請求路由支持。比如,我們可以先讓客戶關(guān)系管理(CRM)系統(tǒng)記錄某個客戶服務(wù)的變更,通知技術(shù)人員配置網(wǎng)絡(luò)等,然后等后臺系統(tǒng)就緒了,再更新路由把流程指向新系統(tǒng)。此外,正如前面提到的,在這個流程中有許多步驟是穩(wěn)定的,比如獲取客戶的人口統(tǒng)計數(shù)據(jù)(姓名、地址等)、為電話提供附加程序或附件等。這些步驟都是可以被幾乎全部銷售過程重用的活動或步驟。在這個場景中添加一個工作流可以極大地提高業(yè)務(wù)響應(yīng)能力并保持業(yè)務(wù)敏捷性。如果某個競爭對手啟動了一個很受歡迎的新方案,那么這家公司就可以在一天之內(nèi)回應(yīng)一個有競爭力的方案。這是真正的有形商業(yè)資產(chǎn)。
工作流引擎的另一個優(yōu)勢是能夠處理持續(xù)的過程。它把涉及多信息交互的全部過程直觀地表示出來,使我們更容易對藍圖和過程有一個清楚的了解,因此可以從業(yè)務(wù)的角度來調(diào)試過程。
當然,工作流化也可以與其它模式結(jié)合。比如,很容易通過作業(yè)調(diào)度(幾乎所有的工作流引擎都支持)實現(xiàn)主動式服務(wù)模式。
流程編排(Orchestrated Choreography)是一種與工作流化密切相關(guān)的模式;這兩種模式都使用相同的底層技術(shù):使用工作流引擎。不過,雖然底層技術(shù)一樣,但是不同的架構(gòu)考慮方式卻會導(dǎo)致選擇不一樣的模式。比如,兩者之間一個很明顯的不同就是工作流化局限于一個單獨的服務(wù)中,而流程編排則需要在服務(wù)間添加調(diào)整性工作流。
技術(shù)相關(guān)
這一部分我們將簡單的了解一下利用當前的技術(shù)實現(xiàn)這個模式的意義以及實現(xiàn)模式所涉及的技術(shù)。
與工作流化模式相關(guān)的技術(shù)自然是工作流引擎。當前市場上有許多工作流引擎。微軟將Windows Workflow Foundation作為.NET 3.0的一部分,我覺得這會讓它在.NET世界中很受歡迎——雖然還有幾家其它公司為.NET提供了像Skelta或K2之類的工作流方案。Java自然能得到更多公司的支持,比如IBM、JBoss,以及專業(yè)的工作流公司Flux等等。Oracle甚至提供了一個工作流包(數(shù)據(jù)庫WF_Engine)和Java API支持。
大多工作流引擎都有內(nèi)置的用以修改工作流的可視化設(shè)計器。圖10是主動服務(wù)模式下用以生成報告的Flux的可視化設(shè)計器。
使用像圖10所示之類的編輯器是修改流程的不錯選擇,通常你還可以使用XML來定義工作流。還有一些工具,比如開源(BSD許可證)OpenWFE,完全不提供可視化編輯器,只能依靠XML來配置工作流。下面是一個在OpenWFE中編輯流程的示例。
例1:OpenWFE中信貸審批工作流的部分XML實現(xiàn)
<process-definition name="Credit approval">
<sequence>
.
.
.
<participant field-ref="order_value" />
<if>
<greater-than field-value="order_value" other-value="10000" />
<!-- then -->
<sequence>
<participant ref=”supervisor”/>
<subprocess ref=”ReviewAndApproveOrder”/>
</sequence>
<!-- else -->
<subprocess ref="TaskPaypal" />
</if>
.
.
.
</sequence>
</process-definition>
選擇工作流引擎——靈活性
編輯工作流可以考慮幾個簡單的模塊,比如活動、異或分支(可能的執(zhí)行路徑之一)、并發(fā)分支。要注意有時候會遇到更復(fù)雜的場景,像如何在不同步的前提下合并多個執(zhí)行路徑,并且只執(zhí)行一次后序的活動,還有如何處理一個活動的(各個活動需要同步的情況、活動數(shù)量無法預(yù)知的情況等等)多個實例,以及許多其它此類問題。這些問題的解決方案就是工作流模式(在“工作流模式頁面”上有描述,詳見http://is.tm.tue.nl/research/patterns/patterns.htm)。
我的建議是先了解一下引擎支持哪些工作流模式以保證良好的靈活性,而不會在后期走進死胡同。雖然靈活性并不是唯一的選擇標準(還得考慮性能、可用性、安全性等),但我覺得作為一個以靈活性為前提而選擇的工具而言,靈活性是一個非常重要的標準。
某些工作流引擎,比如Microsoft Biztalk或WebSphere MQ Workflow,相對內(nèi)部的工作流成本來說,更適合編排內(nèi)部服務(wù)的交互。
質(zhì)量屬性場景
這一部分是從需求的角度討論使用模式的架構(gòu)效益。大多架構(gòu)需求是通過使用場景表現(xiàn)的質(zhì)量屬性(可擴展性、靈活性、性能等)來描述的。這些場景也可供其它可以應(yīng)用模式的情況參考。
工作流化的主要優(yōu)勢是能夠提高靈活性。設(shè)計一個工作流是一個可視化過程(至少大多工作流實現(xiàn)如此),很容易掌握。附加的靈活性優(yōu)勢也能在需求改變時加快上市時間。在我看來,工作流是使服務(wù)走向敏捷業(yè)務(wù)的最重要的工具。
下面是幾個場景,可以給你考慮使用工作流模式的理由。
質(zhì)量屬性(第一層)質(zhì)量屬性(第二層)場景示例
靈活性添加過程對于所有預(yù)付費方案,添加對新方案的支持只需要兩天不到的時間。
重用性核心模塊每個新方案可以重用90%以上的常用銷售過程
由于你可以動態(tài)地改變服務(wù)行為,因此工作流化模式能夠提高服務(wù)的靈活性;另外還可以提高邊界組件模式的靈活性。
模式五:邊界組件
最后一個基本模式是邊界組件模式。稱其它模式為基本模式是因為它們有很大的通用性。但邊界組件模式不同,稱它為基本模式是因為這是一個實現(xiàn)其它模式的平臺。由于邊界組件模式是實現(xiàn)其它模式的一個步驟,具體的示例都是適應(yīng)于在這個邊界組件上構(gòu)建的模式的,所以很難想象一個具體的示例來展示它的必要性。不過,我會嘗試通常幾個簡單的例子和這些例子之間的共性來介紹邊界組件。
問題
場景1
我們曾經(jīng)為一家公司開發(fā)了一個海軍C4I平臺(Military Naval C4I platform)。這個平臺有一些可以重用的服務(wù)。比如,核心服務(wù)之一提供了標準的中央目標視圖。平臺上第一套工具使用了TIBCO Rendezvous消息設(shè)施。后來需要更換完全不同的技術(shù)(WSE 3.0 )。這兩套工具都使用相同的業(yè)務(wù)邏輯,但是實現(xiàn)技術(shù)不同。
場景2
在另一個項目中(在工作流化模式中提到過),一家移動公司經(jīng)常需要在一個處理訂單的服務(wù)中引入新的應(yīng)用和銷售方案,比如朋友和親情、晚間話費等。由于詳細變動都是XML相關(guān),因此這個服務(wù)接口是非常穩(wěn)定的,但是業(yè)務(wù)邏輯卻要為適應(yīng)新方案而經(jīng)常變動。
這是一個與場景1截然相反的場景;這里的接口與技術(shù)是不變的,而業(yè)務(wù)邏輯是變動的。
場景3
最后一個場景是許多項目中常見的一種情況。通常系統(tǒng)里會有多個服務(wù)。雖然每個服務(wù)處理各自不同的業(yè)務(wù),但所有這些服務(wù)都要執(zhí)行一些常見的任務(wù),比如在處理請求之前要確認請求是經(jīng)過驗證的,保存審核條目等等。
在這個場景里,我們遇到了一個不是與單個服務(wù)直接相關(guān)但在各服務(wù)之間重復(fù)性卻是最高的功能——因為即使一個服務(wù)是處理訂單的,另一個服務(wù)是面向顧客的,其記錄請求的代碼都是基本一樣的。
這些場景的共性是每個服務(wù)都涉及多個問題(業(yè)務(wù)邏輯、技術(shù)、記錄等)。正如我們所見到的,所有這些問題都必須可以根據(jù)情況進行變更而不依賴于其它的問題——我們需要實現(xiàn)這種靈活性。因為我們的問題是:
如何讓服務(wù)、技術(shù)和其它交叉問題(比如安全、記錄等)等業(yè)務(wù)方面的問題可以按自己的步調(diào)變更而不產(chǎn)生相互的依賴性?
最簡單的(或許過分簡單了)辦法是不要做任何具體的變動。比如,直接把一部分邏輯當作Web服務(wù)。這對技術(shù)提供商的在線業(yè)務(wù)來說是很常見的,比如Microsoft (WCF)和 Sun (JAX-WS)提供的教程。然而,由于契約操作與業(yè)務(wù)邏輯實現(xiàn)直接糾纏在了一起,這給代碼維護帶來了極大的不便——比如,如果要支持場景1,用這種方法來替換技術(shù)可能就會非常困難。
我們可以通過在復(fù)制服務(wù)上替換新技術(shù)來解決前面的在當前服務(wù)中替換技術(shù)的問題,這種方法也叫“自我克隆(own and clone)”。不過這也會產(chǎn)生維護上的問題,因為你現(xiàn)在有了同一業(yè)務(wù)邏輯的多個復(fù)本,因此你得改動所有復(fù)本,并且這還解決不了場景3里要在多個服務(wù)上添加記錄功能的問題。
如果什么也不做和克隆都行不通,那么我們可能要考慮分開解決各個問題。
解決方案
關(guān)注點分離(SoC)在面向?qū)ο蟮脑O(shè)計中是一個為人熟知的概念。其背后的基本原則是單一責(zé)任原則(The Single Responsibility Principle),或簡稱為SRP。SRP認為要改變一個類只能有唯一的原因,這個原因就是責(zé)任(responsibility)。我們可以在SOA中應(yīng)用同一原則,把業(yè)務(wù)邏輯看作是一個責(zé)任,把其它的問題看作是另一個責(zé)任,這樣我們就得到以下模式:
附加邊界組件(Add Edge Component(s)),用以實現(xiàn)服務(wù)并提高靈活性、分離業(yè)務(wù)邏輯與其它問題(比如契約、協(xié)議、終端技術(shù)和其它交叉問題)。
正如圖11所示,添加邊界組件的主要原因就是關(guān)注點分離。邊界組件可以處理所有這些交叉問題以及其它非核心業(yè)務(wù)問題。這些問題包括負載平衡、格式轉(zhuǎn)換和審計。這樣,服務(wù)的業(yè)務(wù)邏輯就交給了另一個專門處理業(yè)務(wù)邏輯的組件。這種分離支持所有前面提到的場景,因為分離可以允許各個部件自由調(diào)整。比如,要支持一項新技術(shù)(場景1),只需要添加一個邊界組件,但是業(yè)務(wù)邏輯并不需要更換。如果要改變業(yè)務(wù)邏輯的行為,就添加一個新的使用方案(場景2),而邊界組件則不需要更換。
從某種意義來說,邊界組件模式可以為SOA提供外觀(fa?ade)、代理、和AOP模式。
我們還要看一下如何解決場景3中服務(wù)間的交叉問題。最好的辦法是進一步擴展單一責(zé)任原則,并且注意邊界組件實際上是一個組件,不能把它對應(yīng)于整個類型的類。比如,你可以應(yīng)用管道和過濾架構(gòu)類型,把多個類/組件連接到一起,各個類/組件處理特定的問題,以此來創(chuàng)建傳入或傳出的流程。比如,圖12即是一個邊界組件的示例。該示例中,邊界組件提供了一個驗證過濾器來確保消息有正確的格式。然后是一個轉(zhuǎn)換過濾器把外部契約格式轉(zhuǎn)為內(nèi)部格式。最后是一個路由過濾器,負責(zé)把消息發(fā)送到服務(wù)的正確組件。這些組件可以在各個服務(wù)中根據(jù)需求重用,并且能夠自由地進行更改。
雖然從一開始就在邊界組件和服務(wù)之間定義一個內(nèi)部契約很有吸引力,但是實際上沒有理由這么做,除非你必須支持多個外部契約(雖然實現(xiàn)與消費者一對一的契約非常麻煩——見PTP Integration反模式)。如果服務(wù)進展并且創(chuàng)建了新的契約版本,像場景1中像添加新技術(shù),那么在需要支持外部老版本的契約時你可能需要添加內(nèi)部契約。
邊界組件非常有用,我在我設(shè)計的大部分SOA項目中都引入了這個模式。本書中提到的許多架構(gòu)模式也都是基于邊界組件模式的擴展。
下面我們來看看邊界組件模式的技術(shù)相關(guān)問題。
技術(shù)相關(guān)
這一部分我們將簡單的了解一下利用當前的技術(shù)實現(xiàn)這個模式的意義以及實現(xiàn)模式所涉及的技術(shù)。
沒有任何技術(shù)能夠像邊界組件一樣自動處理那么多問題。不過樂觀的說,沒有任何技術(shù)會影響你實現(xiàn)邊界組件模式,而且某些技術(shù)最終將幫你解決一些讓你困惑的問題。
比如,JAX-WS或Windows Communication Foundation (WCF)實際上已經(jīng)為你實現(xiàn)了邊界組件模式,但它們只處理底層的問題,也就是它們稱為綁定(binding)的東西。這些問題是在各種WS*標準中提到的;比如WCF可以處理MTOM encoding或安全問題。然而,你還是需要自己編寫高層的問題,比如路由和契約轉(zhuǎn)換。這一點我在上面已經(jīng)提到過。
還可以使用一種有趣的技術(shù),是稱為Restlet的Java引擎。Restlet有一些內(nèi)置的類,使其成為實現(xiàn)邊界組件模式的優(yōu)秀范例。
邊界組件范例——Restlet引擎
Noelios Consulting公司的Restlet引擎是一個用以實現(xiàn)RESTful服務(wù)的Java庫,它有一些內(nèi)置類(比如filter和router),可以讓我們很方便地構(gòu)建邊界組件。請看圖13的示例。
圖13是一個在訂購服務(wù)上的可行的邊界配置。這個訂購服務(wù)的契約有兩種操作:getLast,返回上一次的訂單;和getAll,返回某個客戶所有的訂單。但是在實際調(diào)用業(yè)務(wù)邏輯之前,我們得先記錄它,處理它的狀態(tài)和情況,然后確定使用了正確的主機,最終調(diào)用正確的業(yè)務(wù)功能。添加一個邊界組件可以讓我們獲得以上效果,并且不會影響到只處理業(yè)務(wù)請求的業(yè)務(wù)邏輯。
下面是上述配置的部分代碼。
例2:使用Restlet定義的邊界組件部分代碼
Builders.buildContainer()
.addServer(Protocol.HTTP, portNumeber)
.attachLog("Log Entry")
.attachStatus(true, "webmaster@mysite.org", "http://www.mysite.org")
.attachHost(portNumber)
.attachRouter("/orders/[+")
.attach("/getAll$", getAllRestlet).owner().start();
.attach("/getLast$", getLastOrderRestlet).owner().start();
現(xiàn)在所有的技術(shù)都支持邊界組件模式,有些甚至已經(jīng)在內(nèi)部實現(xiàn)。
質(zhì)量屬性場景
這一部分是從需求的角度討論使用模式的架構(gòu)效益。大多架構(gòu)需求是通過使用場景表現(xiàn)的質(zhì)量屬性(可擴展性、靈活性、性能等)來描述的。這些場景也可供其它可以應(yīng)用模式的情況參考。
由邊界組件模式與許多質(zhì)量屬性有關(guān)。這些屬性大多是由共同使用邊界組件模式和其它模式產(chǎn)生的結(jié)果。然而,有兩個質(zhì)量屬性是直接與邊界組件模式相關(guān)的。第一個是靈活性——增強服務(wù)的適應(yīng)性,提高服務(wù)的外部屬性,并且不會影響到業(yè)務(wù)邏輯。第二個是易維護性——關(guān)注點分離(SoC)使各組件的行為更易于理解?;叵胍幌律厦嫒齻€場景——在當前服務(wù)中添加新技術(shù)、在不改變契約的前提下改變業(yè)務(wù)行為、以及迅速解決交叉問題——通過邊界組件,我們可以在不影響方案的其它部分(或者至少將影響最小化)的情況下解決問題。下表列出了幾個使用邊界組件的示例場景。
質(zhì)量屬性(第一層)質(zhì)量屬性(第二層)場景示例
易維護性向后兼容性隨著契約的變化,服務(wù)應(yīng)該能夠使用老版本的契約為消費者提供支持(至少前兩個版本)
靈活性擴展點在將來一年之內(nèi),客戶將需要SOX compliance和董事會的審計制度
邊界模式是SOA最基本的架構(gòu)模式。
總結(jié)
最后,來看看我們討論過的這五個構(gòu)建服務(wù)的SOA基本模式。這些模式分別為:
邊界組件:將接口(契約)從實現(xiàn)中分離出來以取得靈活性與可維護性
服務(wù)托管:使用通常包裝器來托管服務(wù)實例并重用
主動式服務(wù):在服務(wù)中使用至少一個獨立線程來啟動
事務(wù)處理服務(wù):處理事務(wù)內(nèi)部的消息并妥善處理故障
工作流化:在服務(wù)中添加工作流以提高靈活性