在過去幾年中,REST風格的軟件架構獲得了越來越多的認可,這主要是因為它減少了系統對動件的需求、同時使系統耦合性更低,彈性更好。
目前越來越多的REST資源出現在企業應用中,因此對這些資源進行編排就顯得非常重要了。比方說,典型的業務活動就包含了資源的創建,然後是資源的查找及其他資源的創建等。
本質上來說,與RESTful服務的交互是相當簡單的:需要構造並發送適當的請求(請求頭和請求體),然後分析返回的響應(響應頭和響應體)。完成這個處理並不需要什麼特別的工具或是框架,只要有一個好用的HTTP客戶端程序庫就足夠了。除此之外,由於RESTful交互過程中所涉及到的不同實體是由所謂的微格式定義的,因此能夠輕松解析或是輸出這些實體的能力就顯得非常重要了。
編排與多個資源的交互是個棘手的問題。我們需要定義編排、處理錯誤並不斷嘗試,同時系統必須能夠在壓力下表現良好。作為一個集成框架,Mule提供了我們所需的一切。
來考慮一個例子吧,某商業系統對新訂單的處理需要如下編排:
通過向服務發送一個XML實體來創建一個新訂單。
尋找新創建的訂單資源並從中提取出確認信息。
根據確認信息並通過郵件網關向客戶發送一條確認消息。
我們將在本文詳細分析上面每一步的交互過程,同時還會介紹為了獲得上述交互所需的Mule動件和Groovy特性。
總體的編排包含了一系列通過特定路由、過濾器以及內存消息隊列(aka VM隊列)連接起來的Mule服務。 最近InfoQ上的這篇文章介紹了Mule中的消息路由,大家不妨移步一觀。
Mule對REST的支持
Mule提供了一種簡單而又強大的方式與RESfFul服務交互,那就是Mule RESTPack。
Mule RESTPack提供了一整套連接器與完整的指南以幫助用戶創建新的RESTful應用。在本文撰寫之際,該軟件包提供了三種傳送器,分別基於三種流行的REST框架:Abdera、Jersey及Restlet。這樣我們就可以輕松公開新的資源,但如何集成現有的REST資源呢?
好消息是Mule標准的腳本模塊所提供的Groovy支持有助於Mule HTTP傳送器的使用,這樣我們就可以成功與RESTful服務交互了。 向Mule發送POST請求
首先來看看第一個交互。HTTP向特定的資源發送一個XML實體來創建訂單,如以下程序片段所示:
POST /orders HTTP 1.1
...
<order xmlns='urn:acme:order:3:1'>
<customerId>123456</customerId>
<productId>P987C</productId>
<quantity>2</quantity>
</order>
如果成功服務器的響應如下:
201 Created
Location: http://acme.com/order/O13579
...
<order id='O13579' />
在Mule中,我們可以通過一個簡單的HTTP輸出端點(outbound endpoint)實現該交互。注意到交互本身是通過向訂單創建服務發送一個包含請求值(客戶與產品ID、數量)的映射而觸發的。該服務如下所示:
<service name="OrderCreationService">
<inbound>
<inbound-endpoint ref="OrderCreationQueue" />
</inbound>
<outbound>
<chaining-router>
<http:outbound-endpoint synchronous="true"
responseTimeout="15" method="POST"
host="${acme.order.hostname}"
port="${acme.order.port}" path="orders"
user="${acme.order.username}"
password="${acme.order.password}"
contentType="application/vnd.acme+xml" encoding="UTF-8">
<transformers>
<transformer ref="OrderMapToMicroformat" />
</transformers>
<response-transformers>
<message-properties-transformer>
<add-message-property key="OrderPlaced"
value="#[groovy:message.getStringProperty('http.status','ERR')=='201']" />
<add-message-property
key="OrderResourceLocation"
value="#[groovy:message.getStringProperty('Location','')]" />
</message-properties-transformer>
<object-to-string-transformer />
</response-transformers>
</http:outbound-endpoint>
<outbound-endpoint ref="OrderCreationResultQueue" />
</chaining-router>
</outbound>
</service>
這全是XML,我們來仔細分析一下:
名為OrderCreationQueue的管道(可以是VM隊列或是JMS隊列)接收消息。
接收到的消息被直接傳遞到另一個路由,該路由會將HTTP POST的結果發送到下一個服務,該服務通過名為OrderCreationResultQueue(異步的VM隊列)的管道對調用結果進行分析。
通過標准的輸出端點在Groovy轉換器上執行該HTTP POST請求:
請求訂單的微格式是通過一個特定的傳送器創建的,下一節將對其進行詳細介紹。
通過一小段腳本將結果代碼抽取出來並與期望值進行比對。進行比較的目的在於將後面的服務與純的HTTP隔離開來:我們所創建的boolean類型的屬性OrderPlaced是獨立的,其名稱與進行的編排密切相關。
類似的,在更具上下文含義的OrderResourceLocation名字下復制Location頭。注意,該頭 有可能丟失(在失敗的情況下),在這種情況下,我們將其值默認設為空字符串以避免將null屬性加到消息中。
我們使用了一個對象——字符串轉換器來“分離”HTTP響應(以流的形式返回)。多虧有了這個轉換器,流得到了完全的處理,其內容也通過使用與 HTTP交換相關的編碼轉換為字符串。當流關閉時,HTTP連接得到了釋放;我們不想一直開著它,等待後面的服務從 OrderCreationResultQueue中拿出響應消息。
Groovy MarkupBuilder的好處
OrderMapToMicroformat轉換器完成了服務中的重頭戲,而它是由Groovy的MarkupBuilder實現的。MarkupBuilder API提供了一種自然的方式生成兼容於特定微格式的XML實體:
<scripting:transformer name="OrderMapToMicroformat">
<scripting:script engine="groovy"> <![CDATA[
def writer = new StringWriter()
def xml = new groovy.xml.MarkupBuilder(writer)
xml.order(xmlns: 'urn:acme:order:3:1') {
customerId(payload.clientId)
productId(payload.productCode)
quantity(payload.quantity)
}
result = writer.toString() ]]>
</scripting:script>
</scripting:transformer>
注意map payload中的值是如何用於組裝XML元素的:這裡解決了一些不匹配的命名約定(比如將clientId轉換為customerId)。
如你所期望的那樣,該轉換器產生了如下輸入:
<order xmlns='urn:acme:order:3:1'>
<customerId>123456</customerId>
<productId>P987C</productId>
<quantity>2</quantity>
</order>
除了正確的內容類型外都是訂單的RESTful服務用於創建新資源所需的內容。
分析
現在我們來看一下負責異步分析訂單創建結果並決定是否需要進一步進行編排的服務:
<service name="OrderCreationResultProcessor">
<inbound>
<inbound-endpoint ref="OrderCreationResultQueue" />
<selective-consumer-router>
<message-property-filter pattern="OrderPlaced=true" />
</selective-consumer-router>
<logging-catch-all-strategy />
</inbound>
<outbound>
<pass-through-router>
<outbound-endpoint ref="SuccessfulOrderQueue" />
</pass-through-router>
</outbound>
</service>
我們使用的選擇性消費者(selective consumer)所接收的消息中一定要包含訂單成功處理的頭信息。如果該頭信息為true,那就通過一個名為SuccessfulOrderQueue 的內存隊列將該消息路由給第三個(也是最終的)服務,該服務會處理訂單成功創建的消息。注意在這個例子中我們只是簡單地將錯誤消息以日志的方式記錄下來,但在實際應用中需要將錯誤消息發送給專門的隊列以進行後續的分析或是及時的反饋。
向Mule發送GET請求
組成該編排的最後一個服務負責HTTP GET處理,它會獲得新創建的訂單,訂單中包含了額外的值以形成一個合法的消息供email網關使用。如下是個示例交互:
GET /order/O13579 HTTP 1.1
200 OK
Content-Type: application/vnd.acme+xml
...
<order xmlns='urn:acme:order:3:1'>
<customerId>123456</customerId>
<productId>P987C</productId>
<quantity>2</quantity>
<customerEmail>[email protected]</customerEmail>
<estimatedShipping>2009-31-12T00:00:00Z</estimatedShipping>
</order>
好消息是Mule的HTTP傳送包含了一個名為rest-servicecomponent的組件,該組件簡化了服務與REST資源的交互。幸好有了這樣一個組件,我們就無需將GET操作的結果發給隨後的服務了,相反可以在單獨的服務中完成一切。除此以外,它還支持在配置中使用表達式,這樣就能實現與動態構建的URL之間的連通了。
<service name="SuccessfulOrderProcessor">
<inbound>
<inbound-endpoint ref="SuccessfulOrderQueue" />
</inbound>
<http:rest-service-component httpMethod="GET"
serviceUrl="#[header:OrderResourceLocation]" />
<outbound>
<pass-through-router>
<outbound-endpoint ref="EmailGatewayQueue">
<transformers>
<object-to-string-transformer />
<transformer ref="OrderMicroformatToEmailMap" />
</transformers>
</outbound-endpoint>
</pass-through-router>
</outbound>
</service>
在接收到成功的訂單消息後,該服務使用REST服務組件生成一個HTTP GET請求,並將該請求發送給此前存儲在OrderResourceLocation屬性(aka heade)中的URL。注意到我們是如何通過Mule表達式框架(使用#[...]語法)在組件中注入動態URL的。
在將GET請求的響應發送給負責與email網關進行通信的服務前,我們對XML訂單實體進行了兩次轉換:
如上所述,我們“分離”了響應實體。
使用轉換器解析XML實體並構建一個映射以供接下來的服務使用。這一次,又利用到了Groovy的強大功能。
Groovy's XmlSlurper Happiness
Groovy的XmlSlurper是解析XML微格式的理想工具,這要歸功於其類似DSL的API。
如下代碼展示了OrderMicroformatToEmailMap轉換器的實現:
<scripting:transformer name="OrderMicroformatToEmailMap">
<scripting:script engine="groovy"><![CDATA[
def order = new XmlSlurper().parseText(payload)
.declareNamespace(acme: 'urn:acme:order:3:1')
result = [emailId:'OrderConfirmation',
emailAddress:order.'acme:customerEmail'.text(),
orderId:[email protected]()]
]]>
</scripting:script>
</scripting:transformer>
沒錯,就兩行Groovy代碼,我們使用了一個命名空間感知的XML解析器和一個map構建器。其好處真是讓人難以置信,不過這就是創建map(email網關服務所期望的)所需的全部內容。
最終的REST
在本文中,我們遵循著一個事先定義好的編排,新訂單資源的URL是唯一的動態元素。開發者可以利用示例中介紹的轉換器和表達式來支持更多的動態交互,如果你打算遵從HATEOAS路線,你就能獲得一切。如果事實如此,那其他的Mule路由就唾手可得了,比如可以創建冪等接收者的路由。你也看到了Groovy的強大功能和靈巧性加上Mule的通信能力極大地簡化了與RESTful服務的交互。Mule的表達式和少量的Groovy 腳本能夠有效地動態定義Mule配置。除此之外,通過使用異步的VM隊列將編排的各個步驟鏈接起來可以在最大負荷下優雅地降級,這要歸功於Mule內在的SEDA架構。
借助於這些強大的工具,集成REST資源變得非常簡單了。
祝你好運!
大家可以從http://dossot.net/datastore/mule-groovy-rest.tar.gz下載完整的配置和相關的測試資源,這是一個獨立的Maven項目。