作者:Michael J. Rettig

前兩個實作使用的整合框架只提供基本的 Message Channel 功能——Axis 與 MSMQ 都只提供收送訊息的 API,其餘幾乎全部得由應用程式自己處理。我們刻意這樣做,是為了示範如何用常見的 Java 或 C# 函式庫從零打造整合方案

許多商業 EAI 產品套件提供多得多的功能來簡化開發:視覺化開發環境讓你能用拖放設定 Message Translator 與 Process Manager,許多還提供成熟的系統管理與中介資料管理功能。

本節選用 TIBCO ActiveEnterprise,同樣把焦點放在設計決策與取捨上,只引入理解方案所必需的產品術語——因此即使你沒用過 TIBCO ActiveEnterprise,這一節仍然有用

這個實作的不同之處#

本實作在設計上採用拍賣式(Auction-style)Scatter-Gather:用 Publish-Subscribe Channel 取代 Recipient List,讓貸款仲介能把報價請求送給任意數量的銀行。這種 Scatter-Gather 執行的是「對未知數量的監聽者做動態 Request/Reply」。

此外,貸款仲介元件使用 TIBCO Process Manager 工具所提供的業務流程管理功能實作。

方案架構#

客戶端期待的是與貸款仲介之間的同步 Request-Reply 介面——送出報價請求後等待回覆;仲介則同樣以 Request-Reply 介面向信用機構取得信用分數。

拍賣的流程:

  1. 把報價請求訊息發布到 bank.loan.request 發布訂閱通道,任何有興趣的一方都能監聽並提供自己的利率
  2. 提交回覆的銀行數量未知,而且每筆報價請求都可能不同
  3. 拍賣的作法是:開放拍賣後,在 bank.loan.reply 通道上等待預先設定的一段時間;每收到一筆出價就把逾時重設,給其他銀行時間再提一次價

在這個情境中,其他銀行的出價是公開的,因此某家銀行其實可以監聽別人的出價、並在想要時提出反制出價。

圖 9-27:TIBCO 的 Loan Broker 方案架構

對 Aggregator 的影響#

仲介是把報價請求廣播給未知數量的接收者——這與 Recipient List「送給預先定義的銀行清單」很不一樣,而這個差別直接反映在 Aggregator 的完成條件上。

Aggregator 不再等每家銀行的回應,而是完全依賴逾時條件來結束拍賣:逾時之後收到的回應一律忽略;若在指定區間內沒有任何銀行回覆,Aggregator 就送一則「未取得任何報價」的回應訊息給客戶端

在本實作中,Content Enricher 與 Aggregator 的功能都實作在 Process Manager 元件內部。因此方案架構圖無法描述元件之間的互動細節(它們被嵌在單一元件裡),我們得改看代表 Process Template 定義的活動圖

一份初始的活動圖既清楚定義了貸款仲介的角色,也構成了 Process Template 的基礎——它以圖形呈現事件的確切順序與決策路徑。

圖 9-28:描述 Process Manager 行為的活動圖

工具組#

延伸:TIB/RendezVous、IntegrationManager 與 TIBCO Repository

TIB/RendezVous 傳輸層#

TIBCO 訊息套件的核心是 TIB/Rendezvous 傳輸層,它在「資訊匯流排」上提供 TIBCO 訊息的收送機制。TIBCO 支援廣泛的傳輸方式(JMS、HTTP、FTP、email 等),本例的底層傳輸就由 Rendezvous 提供。

它同時支援同步與非同步訊息,也支援 Point-to-Point Channel 與 Publish-Subscribe Channel,每個通道可設定不同的服務等級

  • Reliable Messaging(RV)——高效能,但確實有遺失訊息的風險
  • Certified messages(RVCM)——至少送達一次
  • Transactional messaging(RVTX)——保證恰好送達一次

TIB/IntegrationManager(Process Manager 工具)#

IntegrationManager 由設計工作流程的豐富使用者介面執行它們的 Process Manager 引擎組成。GUI 提供大量設定、工作流程與實作選項,全部存放在 TIBCO Repository——TIBCO 用這個儲存庫作為系統的中央組態產物,它保存所有中介資料、工作流程與自訂程式碼

IntegrationManager 可以拆成三部分:channel、job creator、process diagram

  • 每個流程都建立一個「job」(流程實例),作為維護狀態的中央 session 物件。它含有一個帶 get/put 操作的槽位環境,以及與 session 互動的工具方法。
  • **Process Definition(在 TIBCO 中稱 Process Diagram)**指定 job 要執行的任務順序,外觀類似 UML 活動圖——由任務與轉換線構成。

典型的流程圖包含一系列整合任務:控制任務(fork、sync bar、決策點)、訊號任務(收送訊息),以及執行任務(資料轉譯、路由、系統整合)。任務之間的轉換可以含有依訊息內容或其他準則選路的邏輯。

圖 9-29:TIB/IntegrationManager 的組成

圖 9-30:TIB/IntegrationManager 流程圖範例

TIBCO Repository(中介資料管理)#

整合與訊息傳遞幾乎總是需要某種形式的自我描述資料。TIBCO 把中介資料類別定義成存放在 repository 中的 Active Enterprise(AE)物件每個跨通道傳送的 AE 物件都帶有一個 Format Indicator,指明它遵循哪個類別定義

開發者可以直接在開發環境中定義中介資料、從關聯式資料庫這類外部系統萃取(用 Channel Adapter 中描述的 Metadata Adapter),或匯入 XML Schema。中介資料為系統中的物件與訊息提供了明確的契約。

類別定義好之後,就能在 ECMAScript 中實例化與操作:

//Instantiation of a TIBCO AE class
var bank = new aeclass.BankQuoteRequest();
bank.CorrelationID = job.generateGUID();
bank.SSN = job.request.SSN;

但要記得:這是一個動態的腳本環境,編譯期型別檢查有限。改動一個中介資料定義,很容易弄壞系統的另一部分。

沒有適當的測試與開發實務,訊息式系統很容易變成「只能加、不能改」的系統——中介資料只被添加、從不修改,因為大家都怕改壞東西。

圖 9-31:定義帶屬性與操作的 AE 類別

介面與服務等級#

方案需要三種服務:

  • Loan Broker——接收最初的請求、取得信用分數、與銀行舉行貸款拍賣、把最佳報價回傳給客戶端
  • Credit Service——依 SSN 提供信用分數
  • Bank(s)——依信用評等與貸款金額提交報價

每個服務都透過外部介面存取,而每個介面都要做兩個設計決策:對話風格(同步 vs. 非同步)服務品質等級

對話風格已由方案架構決定:貸款仲介與信用服務都需要同步介面,與銀行的通訊則是純非同步。

服務等級在考慮故障切換情境時可能相當複雜,所幸本例可以簡化:一旦失敗(逾時、訊息遺失、系統當機),原始請求可以重送——貸款仲介扮演 Idempotent Receiver。畢竟現階段我們只是取得報價,還不是具法律約束力的協議

當然,這類假設必須被記錄在系統中、並被所有相關方理解——銀行必須知道同一筆報價可能被重送第二次。若銀行為了偵測詐欺而追蹤客戶的貸款請求,我們可能就得修改方案以避免送出重複請求,例如改用 Guaranteed Delivery。

圖 9-32:設定通道屬性

同步服務怎麼實作#

系統有兩個同步介面:客戶 ↔ 貸款仲介、貸款仲介 ↔ 信用機構。

TIBCO 以 Request-ReplyCommand Message 實作 RPC 風格的訊息傳遞——本質上這是底層 TIBCO 訊息引擎的同步包裝。喚起 TIBCO operation 時,你其實能在匯流排上聽到請求與回覆訊息:請求訊息被發布到指定通道(例如 customer.loan.request),訊息中含有一個 Return Address,指定回覆用的所謂 INBOX 通道

圖 9-33:設定 Job Creator

流程模型的實作#

對熟悉 MVC 概念的人來說,這個實作可以用相似的框架理解——只要把 View 換成 Workflow

  1. Workflow——工作流程的視覺化模型
  2. Controller——從訊息匯流排接收事件、執行流程工作流程中對應元件的流程引擎
  3. Model——底層的業務程式碼(ECMAScript、JavaScript、Java、J2EE 等)

用視覺化流程建模工具的一大優勢是:跑起來的「程式碼」看起來與我們設計方案時用的 UML 活動圖非常相似。

流程圖由腳本執行方框與執行整合動作的自訂任務混合而成,逐一走一遍:

1. 建立信用請求物件(ECMAScript)#

var credit = new aeclass.CreditBureauRequest();
credit.SSN = job.request.SSN;
job.creditRequest = credit;

這個信用請求是下一個活動(同步呼叫信用機構)所需的參數。

信用機構被實作成一個獨立的流程圖,只含一個接收訊息並回傳信用分數的 ECMA 任務。同步操作意味著貸款仲介流程會等到信用機構的回覆訊息抵達為止。

2. 建立銀行報價請求(Mapper 任務)#

收到信用機構回覆後,得再建立一個 AE 物件以便向參與的銀行發布報價請求。這裡用 Mapper 任務——它讓我們以圖形方式從多個來源對應資料項,是 Message Translator 模式的視覺化實作

Mapper 任務展現了管理中介資料的好處:因為 IntegrationManager 能取得定義各物件結構的中介資料,Mapper 會顯示來源與目標物件的結構,讓我們用幾下滑鼠就把欄位對應起來。

同樣的功能也可以用 ECMAScript 任務完成:

var bank = new aeclass.BankQuoteRequest();
//Create ID to uniquely identify this transaction.
// We will need this later to filter replies
bank.CorrelationID = job.generateGUID();
bank.SSN = job.request.SSN;
bank.CreditScore = job.creditReply.CreditScore;
bank.HistoryLength = job.creditReply.HistoryLength;
bank.LoanAmount = job.request.LoanAmount;
bank.LoanTerm = job.request.LoanTerm;

job.bankRequest = bank;
  • Mapper 給出來源與目標之間連線的漂亮視圖,但物件欄位一多就變得難讀
  • 另一方面,Mapper 內含特殊函式,能用單一條線對應重複欄位(陣列),不必寫迴圈

圖 9-35:以視覺化 Mapper 任務建立銀行請求訊息

3. 準備出價陣列#

一個只有單行的 ECMAScript 任務:

job.bids = new Array();

4. 發布請求(Signal Out 任務)#

bankRequest 物件發布到 bank.loan.request 通道。

5. 收集出價(Signal In 任務)#

bank.loan.reply 通道上等待進站的報價回覆訊息。若在指定逾時區間內收到訊息,腳本任務就把它加進出價陣列:

job.bids[job.bids.length] = job.loanReply;

6. 選出最佳報價(Aggregator 的聚合演算法)#

拍賣期結束後 Signal In 任務逾時,流程轉往最後一個任務。這個 ECMAScript 任務實作 Aggregator 的聚合演算法——建立一個新的 LoanQuoteReply、把請求物件的 SSN 與 LoanAmount 轉移過來,再走訪出價陣列找出最佳報價:

var loanReply = new aeclass.LoanQuoteReply();
loanReply.SSN = job.request.SSN;
loanReply.LoanAmount = job.request.LoanAmount;

var bids = job.bids;
for (var i = 0; i < bids.length; i++) {
  var item = bids[i];
  if (i == 0 || item.InterestRate < loanReply.InterestRate) {
    loanReply.InterestRate = item.InterestRate;
    loanReply.QuoteID = item.QuoteID;
  }
}

job.loanReply = loanReply;

最後一行把要回傳給客戶的貸款回覆物件放進一個 job 槽位。我們把 job creator 設定成回傳 job 的 loanReply 屬性作為回覆訊息——流程結束時,job creator 就從該屬性取出訊息回傳給客戶端。

圖 9-34:Loan Broker 的流程定義

圖 9-36:設定 Job Creator 的規則集以回傳最佳報價

管理並行拍賣#

拍賣的實作帶來了一些開發障礙——並行讓問題複雜了一個層次。拍賣要能運作,我們得發布一則非同步訊息、再等待指定時間收回覆;但若多場拍賣同時進行,就必須確保每個貸款仲介只收到自己在意的回覆,而不是全部回覆。

作法是用 Correlation Identifier 加上 Selective Consumer 丟掉不相關的訊息。

IntegrationManager 目前的實作限制讓我們無法這麼做:流程圖在執行期被實例化時,必須註冊它所監聽的所有主題,好讓流程引擎能監聽這些主題上的訊息並視需要排隊。

排隊能防止時序 bug——流程圖在某個主題上發布訊息後才轉換到另一個狀態去監聽回覆;在非同步的世界裡,若轉換太慢,回覆可能在流程圖來得及訂閱之前就抵達了。因此流程圖很貼心地把進站訊息排隊,代價是不允許動態主題

以本例的需求而言,在流程層過濾完全夠用。Correlation Identifier 是指派給每個流程的唯一識別碼,傳給每個銀行流程並被包含在每則回覆中;SignalIn 任務中用單行 ECMAScript 完成過濾:

event.msg.CorrelationID == job.bankRequest.CorrelationID;

執行與觀察#

跑起方案只需啟動 IntegrationManager 引擎。測試用的簡單客戶端每 5 秒送一次貸款請求,信用服務與銀行則用簡單的 stub。

從記錄可以看到訊息的時間、主題與 inbox(inbox 就是同步服務的回覆位址):

2003-07-12 16:42:30:
subject=customer.loan.request,
reply=_INBOX.C0A80164.1743F10809898B4B60.3,
SSN=1234567890 LoanAmount=100000.000000 LoanTerm=360

2003-07-12 16:42:30:
subject=credit.loan.request,
reply=_INBOX.C0A80164.1743F10809898B4B60.4,
SSN=1234567890

2003-07-12 16:42:30:
subject=bank.loan.request,
SSN=1234567890 CreditScore=345 HistoryLength=456 LoanAmount=100000.000000
CorrelationID="pUQI3GEWK5Q3d-QiuLzzwGM-zzw" LoanTerm=360

2003-07-12 16:42:30:
subject=bank.loan.reply,
InterestRate=5.017751 QuoteID="5E0x1K_dK5Q3i-QiuMzzwGM-zzw" ErrorCode=0
CorrelationID="pUQI3GEWK5Q3d-QiuLzzwGM-zzw"

2003-07-12 16:42:30:
subject=bank.loan.reply,
InterestRate=5.897514 QuoteID="S9iIAXqgK5Q3n-QiuNzzwGM-zzw" ErrorCode=0
CorrelationID="pUQI3GEWK5Q3d-QiuLzzwGM-zzw"

逐一解讀:

  • 測試客戶端在 customer.loan.request 上的請求訊息,含 SSN、貸款金額與期數(10 萬美元、360 期),並以自己的私有回覆通道作為 Return Address
  • 接著是仲介送給信用服務的請求,同樣帶著仲介的私有 inbox 作為 Return Address。回覆訊息沒被記錄工具捕捉到,因為 _INBOX 通道是私有通道
  • 收到信用分數後,仲介發布訊息到 bank.loan.request;兩個銀行 stub 立刻各回一個利率,並各自指派一個唯一的 QuoteID 供客戶日後查詢
  • 拍賣期在幾秒後逾時

最終回覆(可從流程管理引擎的除錯記錄中看到,其中含有 CorrelationID 與類別的 Format Indicator):

reply= class/LoanQuoteReply {
    SSN=1234567890
    InterestRate=5.017751017038945
    LoanAmount=100000.0
    QuoteID=5E0x1K_dK5Q3i-QiuMzzwGM-zzw
}

結論#

這份彈性的潛在缺點是:它可能掩蓋因主題命名錯誤、開發者失誤或訊息遺失而產生的錯誤。

關於視覺化開發與寫程式的取捨:

有時看起來開發者得在「寫程式」與「用廠商開發工具」之間二選一,但兩者其實常能有效共存:例如用 IntegrationManager 建模整合工作流程,用 J2EE session bean 實作領域邏輯。

為求簡潔,本例相對簡單,因而容易理解;真實世界的實作很少這麼單純。

快速開發工具能加速初期開發,但也可能限制你的開發選項。流程管理工具能幫忙隱藏訊息傳遞的複雜度,讓開發者專注於整合任務、不必操心幕後發生什麼事——然而,對訊息基礎設施的無知已經害死過不只一個專案。