作者:Conrad F. D’Cruz

本節用 Java 與 XML Web Services 實作貸款仲介範例,並使用開源的 Apache Axis 工具組處理 Web Services 的機制細節。

我們不希望這變成 Java 開發練習,選這個工具是為了把設計與實作同步 Web Service 的複雜度抽象掉,好把討論焦點放在「設計同步訊息方案時所做的決策」上。

方案架構#

貸款仲介與方案其餘部分之間有七個重要介面,每一對參與者之間都以 SOAP over HTTP 通訊:

  • 第一個介面是客戶端應用程式送入貸款申請訊息的進入點,也是客戶端接收結果的同一個介面。Axis 伺服器接收客戶端訊息並實作 Service Activator。
  • 第二個介面在仲介與信用機構之間。信用機構是外部單位,提供 Web service 介面讓仲介取得銀行所需的額外客戶資料——仲介用信用機構的資料增補進站請求,這就是 Content Enricher。
  • 接下來五個介面在仲介與五家銀行之間,用來取得利率報價。每個銀行介面功能相同(取得報價),但可能為 SOAP 訊息指定不同格式,因此仲介得先把報價請求轉譯成各家所需的格式。

圖 9-8:Web Services 方案架構

路由與轉換#

仲介以預測式路由直接聯絡每家銀行——參與報價的銀行集合在呼叫之前就已確定,仲介用 Recipient List 把請求送給選定的銀行。

  • 因為每家銀行的請求格式可能不同,仲介需要一組 Message Translator 把請求轉成各家所需的格式
  • 同樣地,每則回覆都要用 Normalizer 轉成共同格式,所有回覆才能被聚合成單一的最佳報價

請求與回應訊息是同步傳遞的——客戶端與貸款仲介都被阻塞,直到每家銀行回應或逾時。仲介把所有回應聚合成單一最佳報價,再把它送回客戶端。

同步訊息在某些需要簡單方案的問題領域中很有用:我們不必操心處理非同步事件、執行緒安全,或支撐它們所需的基礎設施。

Web Services 的設計考量#

XML Web services 建立在 SOAP 之上。

可惜 SOAP 中的「S」(Simple)已經不成立了。作者半開玩笑地提議把它改名為 Complex Remote Access Protocol——縮寫請自行推敲。認真地說,設計一個強健的 Web services 介面需要鑽進一堆術語與相關的設計取捨

傳輸協定#

SOAP 規格的初衷是讓應用程式能以技術中立的方式跨網路做同步 RPC 風格的呼叫,它為每次呼叫定義了各自獨立的請求與回應訊息。

儘管 SOAP 開發時就考慮了訊息傳遞,絕大多數 Web Service 應用仍使用 HTTP 作為傳輸協定——因為它是網路上最常用的協定,而且能溜過防火牆

但 HTTP 是「超文本傳輸協定」——它是為了讓使用者用瀏覽器取回文件而設計的,不是為了讓應用程式互相溝通。HTTP 本質上不可靠,且為同步文件取回而設計:客戶端用同一條連線送出請求並接收回應。

因此使用 HTTP 的 Web Services 幾乎必然採用同步請求/回應訊息——在不可靠的通道上做非同步訊息傳遞,大概跟把瓶子丟進大海差不多有用。

非同步 vs. 同步#

在同步實作中,客戶端連線從送出請求那一刻起就保持開啟,直到伺服器送回回應。

而且若對某個同步服務提供者的呼叫失敗,伺服器應用必須自己提供機制去攔截失敗、復原、重新路由或標記錯誤,才能繼續其他的同步呼叫。

當時多數 Web Services 工具組預設只支援同步訊息;但有些廠商用既有標準與非同步訊息佇列框架模擬出非同步 Web Services,而多個組織與工作小組也已認知到這個需求,正朝標準化前進(例如 WS-ReliableMessaging)。

延伸:編碼風格、繫結風格、可靠性與安全

編碼風格(Encoding Style)#

SOAP 規格以 encodingStyle 屬性指定一個模式,它有兩個值:

  • encoded(SOAP Encoding)——對應 SOAP 規格第 5 節,定義了一套把程式語言型別對應到 XML 的原始機制
  • literal(doc/literal)——別那樣做;型別資訊改由外部機制提供,多半是使用 XML schema 定義訊息中確切型別的 WSDL 文件

之所以有這種區分,是因為 SOAP 規格寫在 XML Schema(XSD)被採用之前——當時沒有公認的型別指定方式,SOAP 只好自己提供一套把型別資訊與參數一起編碼的辦法。

這在**複雜資料型別(例如陣列)**上最明顯:SOAP 規格第 5.4.2 節定義了用特殊 SOAPEnc:Array schema 型別表示陣列的機制。

但自從 XML Schema 被採用後,多數語言各自指定了「XML schema ↔ 程式語言型別」的對應(序列化規則),讓 SOAP encoding 變得多餘——例如 JAX-RPC 規格就唯一地指定了 Java 型別與 XML schema 元素之間的雙向對應。因此 SOAP Encoding 已不再受青睞,被 literal 取代。 >

Apache Axis 簡介#

在我們的貸款仲介應用中,Axis 框架本身就代表訊息通道,其主要功能是代替使用者應用程式處理訊息。

  • Axis 伺服器實作 Service Activator 模式——訊息收到時喚起對應的服務
  • Message Adapter 實作在 Axis 伺服器內,開發者不必自己實作。因此應用程式碼只含業務邏輯,訊息處理服務全由 Axis 負責

運作方式:客戶端把訊息送到端點後,Axis 框架內對應傳輸協定的監聽器建立一個 Message Context 物件(內含實際訊息與傳輸客戶端加上的屬性),並讓它穿過一條請求鏈

Axis 由一系列 Handler 組成,依部署設定與呼叫方(客戶端或伺服器)以特定順序被喚起。Handler 屬於 Message Flow 子系統,被組成 Chain:請求訊息由請求 Handler 鏈處理,回應訊息則走對應的回應鏈。

與本例相關的 Axis 子系統:

  • Message Model——定義 SOAP 訊息的 XML 語法
  • Message Flow——定義傳遞訊息的 Handler 與 Chain
  • Service——定義服務處理器(SOAP、XML-RPC)
  • Transport——提供訊息傳輸的替代方案(HTTP、JMS、SMTP)
  • Provider——定義不同類型類別的提供者(java:RPC、EJB、MDB)

圖 9-9:Axis 框架的內部(Handler 與 Chain)

延伸:三種把 Java 類別部署成 Web service 的方式

一、自動部署(JWS 檔)#

把含有業務邏輯的類別寫成副檔名 *.jws 的 Java Web Service 檔。這個檔案不必編譯,把原始檔複製到伺服器的 webapps 目錄就能立即部署,每個 public 方法都成為可存取的 Web service:

http://hostname:portnumber/axis/LoanBroker.jws

Axis 1.1 會自動從已部署的服務產生 WSDL 文件,讓其他應用程式能檢視遠端介面,也能用來自動產生把 Web service 呼叫封裝進一般 Java 類別的客戶端 stub。

二、使用 WSDD 部署描述子#

WSDD(Web Services Deployment Descriptor)部署編譯過的類別,讓開發者能控制部署參數,例如類別範疇(scope)

  • 預設是 request scope——每收到一個請求就實例化一個新物件,處理完就銷毀
  • 要在整個 session 中持續服務同一客戶的多個請求,就定義成 session scope
  • 要讓所有客戶端存取單例類別(應用程式活著的整段期間都存在),就定義成 application scope

三、從既有 WSDL 產生 proxy 與 skeleton#

wsdl2java 工具產生 proxy 與 skeleton,把所有 SOAP 相關程式碼封裝起來,開發者只要把業務邏輯填進產生的 skeleton 方法主體即可。比前兩者複雜。

本例全部採用第一種(JWS 自動部署),把設計與部署需求保持得盡量簡單;客戶端則手寫呼叫程式碼,刻意減少程式碼生成,因為那會讓範例更難逐步講解

延伸:服務發現與 UDDI

要呼叫部署在伺服器上的 Web service,客戶端必須知道端點 URL。在 Web services 模型中,應用程式應該從共同的服務登錄查出所需服務的位置——UDDI 就是這類儲存庫的標準。

本例把端點 URL 寫死在應用程式裡;但為了讓範例程式好部署,作者為伺服器與客戶端各建了 properties 檔,內含主機名稱與埠號的名值對,讓你能對應到自己的 Axis 安裝。

在任何分散式運算框架中(Java RMI、CORBA 或 SOAP Web services),遠端物件方法呼叫的參數都必須定義成原始資料型別、或能對網路序列化的物件。為了保持簡單,本例用 Java String 物件回傳仲介給客戶端的回應;若呼叫參數是原始型別(intdouble),則必須包進對應的型別包裝類別(IntegerDouble)。

應用程式結構#

核心業務邏輯封裝在 LoanBrokerWS 類別中,它繼承自 Axis 框架提供的類別——該類別實作 Service Activator,在 SOAP 請求抵達時喚起 LoanBrokerWS 的方法

LoanBrokerWS 參照一組 gateway 類別,它們封裝了與外部單位介接的細節:

  • CreditAgencyGateway
  • LenderGateway
  • BankQuoteGateway

對外唯一的服務介面,就是客戶端存取貸款仲介服務的那個訊息端點。不需要另外定義 Messaging Gateway,因為 Axis 框架已經代替應用程式扮演了 gateway 的角色。

元件之間的互動順序:

  1. 仲介先呼叫信用機構 gateway,它用信用分數與信用歷史長度增補所提供的最小資料
  2. 仲介用增補後的資料呼叫放款者 gateway,這個元件實作 Recipient List,用全部資料挑出能服務這筆貸款請求的放款者集合
  3. 仲介接著呼叫銀行報價 gateway,它執行預測式路由,逐一呼叫每家銀行

圖 9-10:Loan Broker 類別圖

圖 9-11:Loan Broker 循序圖

效能限制#

如同前面循序圖所示,取得回應報價的總時間相當長——仲介必須等一家銀行回應後才能送給清單中的下一家,結果就是客戶送出請求後得等很久才拿到報價

作者以單一客戶端對伺服器做基準測試,查詢平均總時間為 8070 毫秒;接著在同一台客戶端機器上開四個客戶端實例並行執行:

客戶端平均時間
Client 112520 ms
Client 212580 ms
Client 315710 ms
Client 413760 ms

這些測試當然稱不上科學嚴謹,但經驗證據明確指出:多個客戶端同時存取這套貸款仲介系統時,效能會嚴重劣化。

本範例的限制#

為了讓討論容易些,作者把所有 Web service 都實作成 JWS 檔——好處是複製過去就能部署。

缺點是 Web service 類別在被呼叫時才實例化、服務一完成就死掉,因此我們觀察到的部分時間落差,其實來自伺服器在喚起服務前建立類別實例的成本。

我們本可以走比較複雜的路——設計一個以 WSDD 部署的 Java class 檔,藉此決定實例化後的類別要存活多久(整個客戶端 session,或整個應用程式期間)。

「Web service 要怎麼部署」在設計真實世界的應用時非常重要;但若把部署議題也納入,範例的說明會變得又臭又長,設計細節也會不必要地複雜。

小結#

本節走過了「用同步 SOAP/HTTP Web services 實作貸款仲介」的過程:我們用預測式路由把貸款請求送給一組銀行,並凸顯了這種作法的優缺點。為了讓討論不至於陷在部署細節裡,我們做了一些設計上的取捨。

整體用意是討論這種作法的優劣,同時也讓我們看到書中許多模式的實際運用——這有助於把「同步預測式」作法移植到其他業務領域