脈絡:假設我們在建一套訂單處理系統。收到訂單後先驗證,再由庫存系統確認商品有貨。這串處理步驟正適合 Pipes and Filters 風格——建兩個過濾器(驗證與庫存),讓訊息依序流過。
但在許多企業整合情境中,庫存系統不只一套,而且每套只能處理特定商品。
為什麼會這樣#
整合方案連接的是既有應用程式,而許多應用程式當初開發時根本沒考慮整合,因此**「一項業務功能被漂亮地封裝在單一系統內」這種理想情境很少見**:
- 併購或商業合作常導致多套系統執行相同的業務功能
- 扮演彙整者或轉售者的企業,通常會介接多套執行相同功能的系統(檢查庫存、下單等)
- 更麻煩的是,這些系統可能在公司內營運,也可能由商業夥伴或加盟者控制
像 Amazon 這類大型電商讓你從書到電鋸到衣服什麼都能訂——依商品類型不同,訂單可能由不同的「幕後」商家的訂單處理系統處理。
假設公司賣 widget 與 gadget,各有一套庫存系統,每項商品有唯一品號。收到訂單時,我們得依商品類型決定把訂單交給哪套庫存系統。
幾條走不通的路#
為不同商品類型各開一個進站通道#
這要求客戶知道我們的內部系統架構——但他們可能根本不曉得我們區分 widget 與 gadget。我們應該把「業務功能實作分散在多套系統」這件事對整合方案的其餘部分(包含客戶)隱藏起來,因此必須預期不同商品的訊息會抵達同一個通道。
廣播給所有庫存系統,讓各系統自行決定#
用 Publish-Subscribe Channel 把訂單轉給所有庫存系統。優點是新增庫存系統很容易,不必改動任何既有元件。
- 如果沒有任何系統能處理這張訂單怎麼辦?
- 如果超過一套系統能處理呢?客戶會不會收到重複出貨?
- 而且許多庫存系統會把「自己處理不了的商品訂單」當成錯誤——那麼每張訂單都會在除了一套之外的所有系統中製造錯誤,很難把這些錯誤與「真正的」錯誤(例如無效訂單)區分開來。
用品號當作通道位址#
每項商品一個專屬通道,客戶直接把訂單發布到該品號對應的通道,庫存系統則監聽自己能處理的那些通道。這利用了通道的可定址性來做路由。
讓訂單依序流經各庫存系統#
第一個能接受訂單的系統就消費該訊息並處理;不能處理就傳給下一個。這消除了「多套系統同時接受」的危險,而且若最後一套系統把訂單傳回來,我們就知道沒有任何系統處理它。
這種作法類似 [GoF] 的 Chain of Responsibility 模式,但在訊息式整合的世界裡,讓訊息穿過一長串系統意味著可觀的開銷;而且它要求各系統彼此協作——若某些系統由外部商業夥伴維護、不在我們控制之下,這根本行不通。
解法#
綜合來說,我們需要的方案要能:封裝「業務功能被拆散」這件事、有效率地使用通道與訊息流量,並確保訂單恰好被一套庫存系統處理。
Content-Based Router 檢視訊息內容,依訊息中的資料把它路由到不同通道。路由可以基於多種準則——欄位是否存在、特定欄位的值等等。
實作時要特別注意讓路由函式容易維護,因為這個路由器很可能成為頻繁維護的痛點。在較複雜的整合情境中,Content-Based Router 可以做成一個可設定的規則引擎,依一組可組態的規則計算目的通道。

圖 7-1:Content-Based Router 解法示意
降低相依性#
Content-Based Router 使用的是預測式路由——它內含所有其他系統能力的知識。這讓路由很有效率,因為每則出站訊息都直接送到正確的系統。
如果讓接收者對路由過程承擔更多控制,就能避免這種相依。這些選項可以概括為反應式過濾——讓每個參與者在訊息經過時自行過濾。
把路由控制分散出去消除了對 Content-Based Router 的需要,但方案通常效率較低。詳細取捨見 Message Filter 與 Routing Slip。
Dynamic Router 描述了兩者之間的折衷:讓每個接收者主動告知路由器自己的能力,路由器維護一份各接收者能力的清單並據此路由。
這份彈性的代價是方案更複雜、除錯也比單純的 Content-Based Router 困難得多。
範例:C# / MSMQ 與 TIBCO MessageBroker
用 C# 與 MSMQ 寫的內容式路由器#
這個範例依訊息內文的第一個字元路由:以 W 開頭送 widgetQueue,以 G 開頭送 gadgetQueue,都不是就送 dunnoQueue——後者其實是 Invalid Message Channel 的一個實例。這個路由器是無狀態的:做路由決定時不「記得」任何先前的訊息。
class ContentBasedRouter
{
protected MessageQueue inQueue;
protected MessageQueue widgetQueue;
protected MessageQueue gadgetQueue;
protected MessageQueue dunnoQueue;
public ContentBasedRouter(MessageQueue inQueue, MessageQueue widgetQueue,
MessageQueue gadgetQueue, MessageQueue dunnoQueue)
{
this.inQueue = inQueue;
this.widgetQueue = widgetQueue;
this.gadgetQueue = gadgetQueue;
this.dunnoQueue = dunnoQueue;
inQueue.ReceiveCompleted += new ReceiveCompletedEventHandler(OnMessage);
inQueue.BeginReceive();
}
private void OnMessage(Object source, ReceiveCompletedEventArgs asyncResult)
{
MessageQueue mq = (MessageQueue)source;
mq.Formatter = new System.Messaging.XmlMessageFormatter(
new String[] {"System.String,mscorlib"});
Message message = mq.EndReceive(asyncResult.AsyncResult);
if (IsWidgetMessage(message))
widgetQueue.Send(message);
else if (IsGadgetMessage(message))
gadgetQueue.Send(message);
else
dunnoQueue.Send(message);
mq.BeginReceive();
}
protected bool IsWidgetMessage (Message message)
{
String text = (String)message.Body;
return (text.StartsWith("W"));
}
protected bool IsGadgetMessage (Message message)
{
String text = (String)message.Body;
return (text.StartsWith("G"));
}
}範例用事件驅動的消費者:把 OnMessage 註冊為 inQueue 上訊息的處理器,佇列的 Formatter 屬性告訴框架我們預期的訊息型別(這裡只處理簡單字串訊息)。OnMessage 決定往哪路由,再呼叫 BeginReceive 表示準備好接收下一則。
為求精簡,這個路由器不具交易性——若它在消費了輸入訊息之後、發布到輸出通道之前崩潰,訊息就遺失了(見 Transactional Client)。
TIBCO MessageBroker#
訊息路由的需求太普遍,多數 EAI 工具套件都提供內建工具來簡化路由邏輯的建構。在 C# 範例中我們得自己寫「從佇列讀訊息、反序列化、分析、再重新發布」的邏輯;在許多 EAI 工具中,這類邏輯用簡單的拖放就能完成,唯一要寫的只有真正的決策邏輯。
TIBCO ActiveEnterprise 套件中的 TIB/MessageBroker 就是一例。訊息流由左至右閱讀:
- 左側(指向右的三角形)是訂閱者元件,從
router.in通道消費訊息 - 訊息內容直接導向右側的發布者元件——從訂閱者的 Data 輸出直連發布者的 Message 輸入,這條直線正代表了「Content-Based Router 不修改訊息」
- 中間的
ComputeSubject函式分析訊息內容以決定正確的輸出通道,它使用一個字典當作「訊息內容 → 目的通道名稱」的對照表:
| 品號代碼 | 通道名稱 |
|---|---|
| G | gadget |
| W | widget |
ComputeSubject 用進站訊息品號的第一個字母查字典,再把結果接在字串 "router.out" 之後,構成如 router.out.widget 的通道名稱:
concat("router.out.", DGet(map, Upper(Left(OrderItem.ItemNumber, 1))))它用 Left 取出品號第一個字母、用 Upper 轉大寫,再用 DGet 以此為鍵從字典取出出站通道名稱。於是品號以 G 開頭的被路由到 router.out.gadget,以 W 開頭的被路由到 router.out.widget。
這個例子展現了商業 EAI 工具的強項:幾十行程式碼縮減成一個函式,而且交易性、執行緒管理、系統管理等特性都免費附帶。
但它也凸顯了「用 UI 工具做出來的方案很難呈現」的困難——我們只能用螢幕截圖來描述,許多重要設定藏在畫面上看不到的屬性欄位裡,這讓文件化變得困難。