本章示範如何把路由與轉換模式組合成一個更大的方案。我們選擇建模「消費者向多家銀行取得貸款報價」這個流程(略作簡化,好把焦點放在整合模式的討論上),並依所定義的模式,用不同的程式語言、技術與訊息模型做出三種替代實作。
情境:取得貸款報價#
尋找貸款時,客戶通常會打電話給好幾家銀行找出利率最好的方案:
- 每家銀行向客戶索取社會安全號碼、貸款金額與希望的期數
- 每家銀行調查客戶的信用背景(通常是聯絡信用機構)
- 依所要求的條件與信用歷史,銀行回覆一份利率報價(或婉拒)
- 客戶收齊所有報價後,挑出最好的(利率最低的)那一份
因為逐一聯絡多家銀行很繁瑣,**貸款仲介(loan broker)**提供這項服務。仲介通常不隸屬任何一家銀行,卻能接觸許多放款機構:它只蒐集一次客戶資料、只聯絡一次信用機構,再依信用分數與歷史把請求送給最合適的幾家銀行,最後收齊報價、選出最好的一份回覆消費者。

圖 9-1:消費者向多家銀行取得貸款報價

圖 9-2:扮演中介者的貸款仲介
設計訊息流#
貸款仲介要執行的個別任務是:
- 接收消費者的貸款報價請求
- 向信用機構取得信用分數與歷史
- 判斷該聯絡哪幾家銀行
- 對每家選定的銀行送出請求
- 收集每家銀行的回應
- 判斷最佳回應
- 把結果回傳給消費者
對應到模式:
- 取得信用分數 → Content Enricher
- 判斷該聯絡哪些銀行 → 另一個 Content Enricher,計算出請求的收件者清單
- 把請求送給多個接收者、再把回應重組成單一訊息 → Scatter-Gather(內部可用 Publish-Subscribe Channel 或 Recipient List 送出請求)
- 把各家報價聚合成一份給消費者的報價 → Aggregator
還有一件事沒處理:每家銀行很可能使用略有不同的訊息格式。為了把路由與聚合邏輯與各家的專有格式分開,我們必須在仲介與銀行之間插入 Message Translator,並用一個 Normalizer 把各家的回應轉譯成共同格式。

圖 9-3:簡易的 Loan Broker 設計

圖 9-4:完整的 Loan Broker 設計
三個關鍵設計決策#
一、時序:同步還是非同步#
- 同步(循序)——仲介向一家銀行要報價、等回應,再問下一家
- 非同步(平行)——仲介一次送出所有報價請求,然後等答案回來
非同步方案一次送出所有請求,讓每家銀行同時開始處理。若各家耗時相近,這個方案幾乎快 n 倍(n 為銀行數)。代價是:仲介必須能以任意順序接受報價訊息,因為不保證回應順序與請求順序相同。
透過訊息佇列做非同步呼叫還有一個重要優勢:能建立同一個服務的多個實例。例如若信用機構成了瓶頸,我們可以跑兩個信用機構元件——因為仲介是把請求訊息送到佇列而非直接送給元件,由哪個實例處理都無所謂,只要回應被放回回覆通道就好。

圖 9-5:同步、循序處理貸款請求

圖 9-6:非同步、平行處理貸款請求
二、定址:分派還是拍賣#
Scatter-Gather 可以用 Recipient List 或 Publish-Subscribe Channel,選擇主要取決於我們想對「哪些銀行能參與某筆貸款請求」施加多少控制:
- 固定(Fixed)——銀行清單寫死,每筆請求都送給同一組銀行
- 分派(Distribution)——仲介維護「哪些銀行適合哪類請求」的準則。例如信用不良的客戶就不會被送到專做頂級客戶的銀行
- 拍賣(Auction)——仲介用 Publish-Subscribe Channel 廣播請求,任何有興趣的銀行都能訂閱通道並「出價」,銀行可隨意訂閱或取消訂閱,也仍能套用自己的準則決定是否投標
各自的取捨:
注意:許多高效率的發布訂閱機制使用 IP Multicast,而它通常無法跨廣域網路或網際網路路由;其他實作則以「一組點對點通道 + Recipient List」模擬發布訂閱——語意簡單,但通道與網路頻寬的使用效率較差。

圖 9-7:三種定址方式:固定、分派與拍賣
三、聚合:多通道還是單通道#
- 單一回覆通道——減少「為每家參與銀行各設一個通道」的維護負擔,但要求每則銀行回覆訊息含有一個欄位標明是哪家銀行出的報價。
若用單一回覆通道,Aggregator 可能不知道該期待幾則回應——除非 Recipient List 把這個資訊傳給它(也就是 Initialized Aggregator)。
若採拍賣式發布訂閱,仲介根本不知道可能有幾個回應,Aggregator 就必須採用「不依賴參與者總數」的完成條件。例如「等到至少收到三則回應」——但萬一當下只有兩家銀行參與,這仍有風險;此時 Aggregator 可以逾時並回報「收到的回應數量不足」。
管理並行#
貸款仲介這類服務應該能同時服務多個客戶端。
若把它做成 Web service 或接上公開網站,我們對客戶端數量幾乎沒有控制,可能收到數百或數千個並行請求。
有兩種策略:
執行多個實例#
為每則進站請求啟動一個新實例,或維護一個活躍行程的「池」,用 Message Dispatcher 把進站請求指派給下一個可用行程;沒有可用行程時就把請求排隊。
單一事件驅動的實例#
因為貸款仲介的多數「處理」其實是在等外部單位(信用機構與銀行)的回覆,跑很多平行行程可能不是好的資源運用。
我們可以只跑一個實例,隨進站訊息事件抵達而反應。處理單一則訊息(例如一份銀行報價)是相對簡單的任務,因此單一行程可能就足以服務許多並行請求——資源用得更有效率,而且只需監控一個行程實例,管理也更簡單。
潛在缺點是擴展性受限於單一行程。許多高流量應用會結合兩種技巧:跑多個平行行程,而每個行程各自能並行處理多個請求。
執行並行請求時,我們必須把系統中的每則訊息關聯到正確的流程實例。例如銀行可能覺得「把所有回覆送到固定通道」最方便,這代表回覆通道上會混著不同客戶的並行報價請求——因此每則訊息都需要 Correlation Identifier,才能辨識銀行回應的是哪一筆客戶請求。
三種實作#
| 實作 | 時序 | 定址 | 聚合 | 通道型別 | 產品/技術 |
|---|---|---|---|---|---|
| A | 同步 | 分派 | 依通道 | Web Service / SOAP | Java / Apache Axis |
| B | 非同步 | 分派 | Correlation ID | 訊息佇列 | C# / Microsoft MSMQ |
| C | 非同步 | 拍賣 | Correlation ID | 發布訂閱 | TIBCO ActiveEnterprise |
- 實作 A 用同步 Web Services(Java + Apache Axis)。與每家銀行的通訊走各自獨立的 HTTP 通道,同時作為請求與回覆通道;因此聚合策略建立在各自獨立的回覆通道上,不需要關聯。
- 實作 B 用訊息佇列的非同步作法(MSMQ;若用 JMS Queue 或 IBM WebSphere MQ,寫法會非常相似)。
- 實作 C 採拍賣作法,運用 TIBCO 的發布訂閱基礎設施與 TIB/IntegrationManager 流程管理工具。