理解訊息式整合方案最好的方法,是走過一個具體案例。我們來看 Widgets & Gadgets ’R Us(WGRUS)——一家線上零售商,向製造商採購 widget 與 gadget,再轉賣給客戶。

需求與現有系統#

方案需要支援以下需求(為求簡潔已略作簡化,但這類需求在真實企業中反覆出現):

  • 接單——客戶可透過網站、電話或傳真下訂單
  • 處理訂單——包含驗證庫存、出貨、開立發票等多個步驟
  • 查詢狀態——客戶可查詢訂單狀態
  • 變更地址——客戶可透過 Web 前端變更帳單與收貨地址
  • 更新型錄——供應商定期更新型錄,WGRUS 需依此更新價格與供貨狀況
  • 公告——客戶可訂閱有選擇性的 WGRUS 公告
  • 測試與監控——維運人員需能監控所有元件與元件之間的訊息流

和多數整合情境一樣,WGRUS 不是從零開始的「綠地」專案,而是要整合一套既有的 IT 基礎設施——由各種套裝與自製應用程式混合而成。必須遷就既有應用程式,正是整合工作困難的原因之一。

WGRUS 的系統版圖:

  • 四條客戶接觸管道——公司網站、客服中心(電話)、傳真下單,以及 e-mail 通知。
  • 會計系統——同時包含帳務功能。
  • 出貨系統——計算運費並與貨運公司互動。
  • 兩套庫存與型錄系統——WGRUS 原本只賣 widget,後來併購了一家賣 gadget 的零售商。他們判斷平行運作兩套系統,會比把兩者改寫成單一系統便宜。

圖 1-10:WGRUS 的生態系

圖 1-11:WGRUS 的 IT 基礎設施

接單#

接單是好事,因為它帶進營收。但目前下單是繁瑣的人工流程,每筆訂單成本很高——20 美元以下的訂單,利潤幾乎全被處理訂單的人力成本吃光

第一步是把接單統一起來。三條管道各自基於不同技術、以不同資料格式儲存進來的訂單:客服中心是套裝應用、網站是自製的 J2EE 應用、傳真則要人工輸入到一個小型 Microsoft Access 應用。但我們希望所有訂單被平等對待——客戶應該能從客服中心下單,然後在網站上查狀態。

因為下訂單是一個串起許多系統的非同步流程,我們決定用訊息導向中介軟體來理順它:

  • 套裝的客服中心應用當初沒考慮整合,所以用 Channel Adapter(通道轉接器) 把它接上訊息系統。Channel Adapter 能附著在應用程式上,在應用程式內部發生事件時把訊息發布到 Message Channel。有些 Channel Adapter 甚至讓應用程式毫無察覺——例如資料庫轉接器可以在特定資料表加觸發器,應用程式每插入一列資料就送出一則訊息。它也能反向運作:從通道消費訊息,並觸發應用程式內部的動作。
  • 傳真應用用同樣手法,把 Channel Adapter 接到它的資料庫。
  • Web 應用是自製的,所以直接把端點程式碼實作在應用程式裡,並用 Messaging Gateway(訊息閘道) 把應用程式邏輯與訊息相關的程式碼隔離開。

用 Canonical Data Model 統一格式#

三套系統的訂單格式各異,因此我們用三個 Message Translator(訊息轉換器) 把它們轉成共同的 New Order 訊息,遵循 Canonical Data Model(正規資料模型)

Canonical Data Model 定義的訊息格式獨立於任何特定應用程式。若某個應用程式的內部格式改變,只需修改該應用程式與通道之間的那個 Message Translator,其他應用程式與轉換器完全不受影響。

採用 Canonical Data Model 意味著我們會有兩類訊息:

  • 正規(公開)訊息
  • 應用程式專屬(私有)訊息——除了對應的應用程式與其 Message Translator 之外,不該被任何元件消費

為了強化這個政策,應用程式專屬的通道以應用程式名稱開頭命名(如 WEB_NEW_ORDER),承載正規訊息的通道則直接以訊息意圖命名、不加前綴(如 NEW_ORDER)。

通道與訊息的型別#

  • 每個 Channel Adapter 與 Message Translator 之間用 Point-to-Point Channel(點對點通道)相連,確保每則訂單訊息只被消費一次
  • 所有 Message Translator 都發布到同一個 NEW_ORDER 點對點通道,於是後續處理不必在意訂單來自哪裡。
  • NEW_ORDER 是一個 Datatype Channel(資料型別通道)——它只承載單一型別的訊息(新訂單),消費端因此清楚知道會收到什麼。
  • New Order 訊息本身設計成 Document Message(文件訊息):它的意圖不是指示收件端執行特定動作,而是把一份文件交給任何有興趣的接收者,由對方自行決定怎麼處理。

Web 介面其實可以把轉換邏輯寫進 Gateway、省掉一個 Message Translator。但手寫轉換函式既繁瑣又容易出錯,作法一致性更有價值;額外那個 Translator 也能替 New Order 流程擋掉 Web 介面資料格式的小幅變動。

圖 1-12:從三條不同管道接單

處理訂單#

要履行一筆訂單,需要完成以下步驟:

  • 驗證客戶的信用狀況——若客戶有未付帳單,就退回新訂單
  • 驗證庫存——沒庫存的商品無法履行
  • 若客戶狀況良好且有庫存,就出貨並開立帳單

這串事件可以用 UML 活動圖表達。活動圖語意簡單,很適合描繪含平行活動的流程:後續活動以箭頭相連,平行活動則以粗黑條表示 fork 與 join;fork 讓所有相連活動同時開始,join 則等所有進入的活動都完成後才繼續。

從活動圖映射到訊息設計#

活動與 WGRUS 的系統對得相當整齊:會計系統驗證信用狀況、庫存系統檢查庫存、出貨系統啟動實體出貨,會計系統同時兼任帳務系統寄發票。訂單處理是典型的分散式商業流程實作。

要把邏輯活動圖轉成整合設計:

  • Publish-Subscribe Channel(發布訂閱通道) 實作 fork——它把訊息送給所有活躍的消費者
  • Aggregator(聚合器) 實作 join——它接收多則進站訊息並合併成單一出站訊息
  • Content-Based Router(內容式路由器) 實作分支——它消費訊息,並依內建規則原封不動地發布到多個通道之一

Aggregator 合併兩則訊息的結果後交給 Content-Based Router:庫存檢查與信用檢查都通過,就轉送到 VALIDATED_ORDER 通道;否則轉送到 INVALID_ORDER,由例外流程監聽該通道並通知客戶訂單被拒。

圖 1-14:以非同步訊息實作訂單處理

路由到正確的庫存系統#

WGRUS 有兩套庫存系統(widget 與 gadget),因此庫存請求必須被導向正確的系統。為了把庫存系統的特異之處對其他系統藏起來,我們插入一個 Content-Based Router,依商品類型路由——品號以 W 開頭的送到 widget 庫存系統,以 G 開頭的送到 gadget 庫存系統。

注意 Content-Based Router 與庫存系統之間那些點對點通道上的訊息意圖不同了:它們承載的是 Command Message(命令訊息)——指示系統執行特定命令(此處是驗證某商品的庫存)。

兩套庫存系統內部格式不同,所以再插入 Message Translator 把正規的 New Order 格式轉成系統專屬格式。在每個來源系統(Web、客服中心、傳真)與每個目標系統(兩套庫存)都放 Translator,讓系統之間的變動彼此解耦——例如新增 e-mail 下單管道時,其他系統一概不受影響。

這份彈性的代價是:每則訊息要翻譯兩次,一次在來源端、一次在目的端。

若品號既不是 W 也不是 G 開頭呢?Content-Based Router 會把訊息路由到 INVALID_ORDER 通道——這是 Invalid Message Channel(無效訊息通道) 的典型例子。

這凸顯一件事:訊息的意義會隨它所在的通道而改變。 NEW_ORDERINVALID_ORDER 承載的是同一型別的訊息,但在一個通道上代表「正在處理新訂單」,在另一個上代表「這筆訂單被判定無效」。

圖 1-15:路由庫存請求

一張訂單多項商品:Splitter 與 Aggregator#

前面假設每張訂單只有一項商品,這對客戶很不方便(每項商品都得下一張新訂單),對我們也不划算(同一位客戶要出多次貨,白付運費)。

但若一張訂單能有多項商品,該由哪套庫存系統驗證?用 Publish-Subscribe Channel 把訂單送給每套庫存系統、各自挑走能處理的商品?那無效商品怎麼辦——我們要怎麼察覺某項商品兩套系統都沒處理?

我們希望保留 Content-Based Router 給我們的集中控制,同時能逐項路由。因此插入 Splitter(分割器):把單一訊息拆成多則個別訊息——這裡是把一則 Order 訊息拆成多則 Order Item 訊息,每則再各自由 Content-Based Router 路由到正確的庫存系統。

所有商品的庫存都驗證完之後,自然要把訊息重新合併成一則——這正是 Aggregator 的工作。Splitter 與 Aggregator 搭配使用,讓訂單項目的訊息流與訂單本身的訊息流在邏輯上分離。

設計 Aggregator 時要做三個關鍵決定:

  • 關聯(correlation)——哪些訊息屬於同一組?
  • 完成條件(completeness condition)——怎麼判斷訊息都收齊了?
  • 聚合演算法(aggregation algorithm)——如何把個別訊息組合成單一結果訊息?

我們不能用客戶 ID 來關聯訂單項目,因為同一位客戶可能短時間內下多張訂單。因此每張訂單需要一個唯一的訂單 ID——作法是在接單流程中插入一個 Content Enricher(內容增益器),替進站訊息補上缺少的資料項。

有了訂單 ID 之後:

  • 完成條件——因為所有訊息(包含無效商品)都會送到 Aggregator,它可以直接用訂單訊息中的「商品項數」欄位來計數,數到全部到齊為止。
  • 聚合演算法——把所有商品訊息串接回單一訂單訊息,發布到 VALIDATED_ORDER 通道。

圖 1-13:訂單處理的活動圖

圖 1-16:逐項處理訂單項目

圖 1-17:加入 Content Enricher 的接單流程

圖 1-18:修訂後的訂單處理實作

查詢狀態#

即使系統之間以訊息通道相連,履行一筆訂單仍可能耗時。例如某項商品缺貨,庫存系統可能會把庫存檢查訊息「壓著」等新貨到。

這正是非同步訊息的優點之一:通訊按各元件自己的節奏發生。庫存系統壓著訊息的同時,會計系統仍能驗證客戶信用;兩步都完成後,Aggregator 才發布 Validated Order 訊息,啟動出貨與開立發票。

長時間執行的商業流程也意味著客戶與管理者會想知道特定訂單的狀態。以目前的設計,追蹤狀態並不容易——相關訊息流經多套系統,要判斷狀態就得知道與這張訂單相關的「最後一則」訊息。

  • 對 Publish-Subscribe Channel:優點是可以在不擾動既有訊息流的前提下加訂閱者。我們利用這點監聽新訂單與已驗證訂單,把它們存進 Message Store(訊息儲存庫),再查詢這個資料庫取得狀態。
  • 對 Point-to-Point Channel:不能單純加訂閱者,因為每則訊息只能被一個訂閱者消費。此時插入 Wire Tap(線路竊聽器)——一個從一個通道消費訊息、再發布到兩個通道的簡單元件;用第二個通道把訊息送進 Message Store。

Message Store 帶來的額外好處#

把訊息資料存進中央資料庫還有一個重要優勢。原本的設計中,每則訊息都得攜帶所有後續處理所需的資料——例如「驗證客戶信用」其實只需要客戶 ID,卻得一路帶著各種客戶資料,只為了讓結果訊息仍包含原始訂單的全部資料。

把 New Order 訊息存進 Message Store 之後,所有後續元件都能回頭向它索取重要資料,中間步驟不必再一路揹著

這個作法後續被稱為 Claim Check(寄物單)——訊息可以把資料「寄放」起來,稍後再取回。

圖 1-19:加入 Message Store 以追蹤訂單狀態

圖 1-20:用 Wire Tap 追蹤訊息

從 Message Store 到 Process Manager#

現在 Message Store 同時負責保存新訊息的相關資料,以及訊息在流程中的進度。這些資料足以讓我們用 Message Store 本身來決定流程的下一步,而不必用固定的 Message Channel 把元件串死。

例如:若資料庫裡已有來自兩套庫存系統與帳務系統的回覆訊息,就能斷定訂單已通過驗證,接著送訊息給出貨與帳務系統。與其把這個決策放在獨立的 Aggregator,不如直接在 Message Store 裡做——於是 Message Store 變成了 Process Manager(流程管理器)

Process Manager 是管理訊息在系統中流動的中央元件,提供兩項主要功能:

  • 在訊息之間保存資料
  • 追蹤進度並決定下一步

這個架構把個別系統(例如庫存系統)變成可被任何流程存取的共享服務(Shared Services),提升重用性、也讓變更與維護更快速。服務本身仍可由多個步驟組成,用訊息流串接(例如用 Composed Message Processor 逐項檢查庫存),或由 Process Manager 編排。

Process Manager 自己用持久化儲存(通常是檔案或關聯式資料庫)保存每個流程實例的資料。至於 Web 介面要查訂單狀態——因為查狀態是同步流程(客戶當下就要看到回應),而 Web 介面又是自製應用,我們決定讓它直接讀取訂單資料庫

這種 Shared Database 形式最簡單、最有效率,也保證 Web 介面顯示的一定是最新狀態。潛在缺點是 Web 介面與資料庫緊密耦合——這是我們願意接受的取捨。

走向 SOA:Return Address 與 Smart Proxy#

新架構把所有服務暴露在共同的服務匯流排上,供任何元件呼叫。若再加上從服務登錄中查找(「發現」)服務的機制,WGRUS 的 IT 基礎設施就成了服務導向架構。

要參與 SOA,每個服務得多提供一些功能:

  • 暴露描述自身功能的介面契約
  • 每個請求-回覆式服務都要支援 Return Address(回覆位址) 的概念——讓呼叫端(服務消費者)指定服務該把回覆訊息送到哪個通道。這對服務能在不同情境下被重用很關鍵,因為每個情境可能需要自己的回覆通道。

難處在於:許多遺留系統當初根本沒有 Return Address 這種概念。因此我們用 Smart Proxy(智慧代理) 把存取遺留系統的路徑「包」起來,替基礎服務加上額外能力,使它能參與 SOA。Smart Proxy 的作法是同時攔截請求與回覆訊息:它從請求訊息中保存資訊(例如請求方指定的 Return Address),再用這些資訊處理回覆訊息(例如路由到正確的回覆通道)。

Smart Proxy 也非常適合用來追蹤外部服務的服務品質(例如回應時間)。

圖 1-21:以 Process Manager 處理訂單

圖 1-22:插入 Smart Proxy 把遺留系統變成共享服務

變更地址#

WGRUS 要處理好幾種地址:發票寄到客戶的帳單地址,貨品寄到收貨地址。我們希望客戶能透過 Web 介面自行維護這些地址,消除不必要的人工步驟。

把正確的地址送到帳務與出貨系統,有兩種基本作法:

選項一:把地址資料包進 New Order 訊息#

優點是可以沿用既有的整合通道傳輸這些額外資訊。潛在缺點是中介軟體上多流動一份資料——每張訂單都帶著地址,即使地址的變動頻率遠低於下單頻率。

由於帳務與出貨系統是套裝應用、當初沒有為整合而設計,它們多半無法「跟著新訂單接受地址」,而是使用自己本地資料庫裡存的地址。因此要在帳務(或出貨)系統執行兩個動作:先更新地址,再寄帳單(或出貨)。因為兩則訊息的順序有意義,我們插入一個簡單的 Process Manager 元件:它接收 New Order 訊息(含當前的收貨與帳單地址),再發布兩則獨立訊息給帳務(或出貨)系統。

  • 地址用資料庫 Channel Adapter 直接寫進系統資料庫。
  • 出貨或開立發票必須觸發應用程式的商業邏輯,因此接到應用程式的業務層、在收到訊息時呼叫正確的 API 函式。
  • Channel Adapter 要求訊息使用應用程式的專有格式(私有訊息),而 New Order 是正規格式,因此中間需要轉換。我們偏好使用外部的 Message Translator,而不是把轉換寫進 Process Manager——這樣 Process Manager 的邏輯就不會被應用程式那些可能很麻煩的資料格式牽連。

圖 1-23:把地址資料包進 New Order 訊息

選項二:用資料複寫傳播地址變更#

Web 介面裡地址一改變,就用 Publish-Subscribe Channel 把變更傳播給所有有興趣的系統,各系統把新地址存在內部,等訂單訊息來時再使用。

  • 優點:減少訊息流量(假設客戶改地址的頻率低於下單頻率),也降低系統間的耦合——任何用到地址的系統都能訂閱 ADDRESS_CHANGE 通道,而不影響其他系統。
  • 缺點:得替帳務與出貨系統另外做一個能消費地址變更訊息的介面。

因為地址有多種類型(收貨與帳單),必須確保各系統只存到正確的那一種——不能把帳單地址的變更訊息送到出貨系統。作法是用 Message Filter(訊息過濾器),只放行符合特定條件的訊息,並同樣用 Message Translator 把通用的 Address Change 訊息轉成各應用程式的專屬格式。

這裡 Web 介面不需要 Message Translator,因為我們把 Canonical Data Model 直接定義成等同 Web 介面應用的格式。這會限制未來新增其他改地址管道時的彈性,但目前夠用。

圖 1-24:以獨立的發布訂閱通道傳播地址變更

怎麼選?#

本案例中訊息流量不是問題(一天只處理幾百張訂單),兩種方案都可行,主要決策依據是應用程式的內部結構

我們可能無法直接把地址寫進資料庫,而必須經過應用程式的業務層。此時應用程式可能會執行額外驗證、記錄地址變更活動,甚至被設定成每次地址變更就寄一封確認信給客戶——如果每下一張訂單就更新一次地址,這會非常惱人。這種情況就偏向選項二:只在客戶真的改地址時,用專屬訊息傳播。

粒度的取捨#

一般而言,我們偏好「變更地址」「下訂單」這種定義明確、自我完備的商業動作,因為它們在編排商業流程時彈性更大。這終究是粒度與取捨的問題:

  • 過細的介面會讓系統遲鈍——遠端呼叫或訊息數量過多。想像一個為每個地址欄位各開一個方法的介面:在單一應用程式內部很有效率(只更新真的改了的欄位),但在整合情境下,送六、七則訊息只為更新一個地址,開銷驚人,還得處理個別訊息之間的同步。過細的介面也導致緊密耦合——地址格式一改,就得定義新訊息格式並修改所有應用程式。
  • 過粗的介面能解決上述問題(訊息更少、更有效率、耦合更鬆),但也會限制彈性——如果「寄發票」與「變更地址」被合成單一外部功能,難道我們永遠不會需要「只改地址、不寄帳單」嗎?

一如既往,最好的答案是中庸之道,取決於真實情境中實際起作用的那些取捨。

更新型錄#

客戶要下單就得先在線上看到目前供應的商品與價格。WGRUS 的型錄由各供應商的供貨內容驅動,而 WGRUS 提供給客戶的一項服務,就是讓他們在同一個網站瀏覽 widget 與 gadget,並在一張訂單裡同時訂購兩者——這是典型的資訊入口情境:把多個來源的資訊合併成單一視圖。

兩家供應商都每三個月才更新一次產品型錄,因此建立即時訊息基礎設施來傳播型錄變更意義不大。我們改用 **File Transfer(檔案傳輸)**整合,把型錄資料從供應商搬到 WGRUS。

用檔案還有另一個好處:檔案能以 FTP 或類似協定,輕鬆且有效率地跨公用網路傳輸。相較之下,多數非同步訊息基礎設施在公開網際網路上表現不佳。

我們仍可用 Translator 與 Adapter 把資料轉成內部型錄格式,只是這些 Translator 一次處理整份型錄,而不是逐項處理——面對大量同格式資料時,這樣有效率得多。

圖 1-25:以檔案傳輸更新型錄資料

公告#

為了促進業績,我們想不時向客戶發布特惠公告。為了不惹惱客戶,只寄他們有興趣的訊息;同時也要能把特定訊息鎖定特定客群(例如只對優質客戶宣布特別優惠)。

要把資訊送給多個接收者,Publish-Subscribe Channel 是直覺的選擇,但它有兩個缺點:

  • 任何訂閱者都能在發布者不知情的狀況下收聽已發布的訊息——我們可不希望小型客戶收到給大量採購客戶的特殊優惠。
  • 只在區域網路上有效率。跨廣域網路時得對每個接收者各送一份副本;若某個接收者對訊息沒興趣,那份流量就白費了。

因此我們需要的是:讓訂閱者提出訂閱偏好,然後只把個別訊息送給有興趣(且經授權)的客戶。這由 Dynamic Recipient List(動態接收者清單) 達成——它是兩個路由模式的組合:

  • Recipient List(接收者清單)——把單一訊息傳播給一組接收者。它與 Publish-Subscribe Channel 的關鍵差別在於,Recipient List 明確定址每一個接收者,因此能嚴密控制誰收得到訊息。
  • Dynamic Router(動態路由器)——其路由演算法可依控制訊息而改變;這些控制訊息就是訂閱者提出的訂閱偏好。

若客戶透過 e-mail 收公告,這些模式可以直接用郵件系統的通訊群組功能實作,每個接收者通道就以一個 e-mail 位址識別;若客戶偏好透過 Web service 介面接收,每個接收者通道就以一次 SOAP 請求實作,通道位址就是該 Web service 的 URI。

這個例子說明了:我們用來描述設計的模式,與特定傳輸技術無關。

圖 1-26:以 Dynamic Recipient List 發送公告

測試與監控#

監控訊息是否正確執行,是關鍵的維運與支援功能。Message Store 能提供一些重要的業務指標(例如平均履單時間),但成功維運一套整合方案往往需要更細的資訊。

假設我們擴充方案,改為存取外部信用機構來更好地評估客戶信用:即使客戶沒有未付款項,若信用評分特別差,我們仍可能拒絕訂單(對新客戶尤其有用)。因為這是外部供應商提供的服務,我們要按使用量付費。

要核對供應商的帳單,就得追蹤實際用量並與對方報表對帳。我們不能單純用訂單數推算,因為商業邏輯對長期客戶可能根本不會發出外部信用查詢;而且我們與外部供應商簽有服務品質協議(QoS)——回應時間超過約定時限的請求,不必付費

因此我們要追蹤請求數量與對應回覆抵達所需的時間,並處理兩個特定狀況:

  • 外部服務能同時處理多個請求,所以我們必須能把請求與回覆配對起來
  • 由於我們把這個外部服務當成企業內部的共享服務,要允許服務消費者指定 Return Address。若不知道回覆訊息會出現在哪個通道,配對就很困難。

Smart Proxy 再次是答案。 我們把它插在服務消費者與外部服務之間:

  1. 把消費者指定的 Return Address 換成一個固定的回覆通道
  2. 把原始的 Return Address 存在 Smart Proxy 內,之後據此把回覆訊息轉送到消費者指定的通道
  3. 測量請求與回覆之間經過的時間,並把資料發布到 Control Bus(控制匯流排);Control Bus 連到一個從眾多元件收集指標的管理主控台

偵測「格式正確但內容錯誤」的回覆#

Smart Proxy 也能把「在逾時期限內沒收到回覆」的情況回報給管理主控台。更難偵測的是:外部服務確實回了訊息,但內容是錯的

如果外部服務故障、對每位客戶都回傳信用分數 0,我們會拒絕掉每一張訂單。

有兩種機制可以防範:

  • 注入測試訊息——定期在請求流中插入 Test Message(測試訊息),查詢某個已知結果的對象,再用 Test Data Verifier(測試資料驗證器) 不只確認「有收到回覆」,更確認回覆內容是否正確。因為 Smart Proxy 支援 Return Address,Test Data Generator 可以指定一個特別的回覆通道,把測試回覆與正式回覆分開。
  • 統計抽樣——例如我們預期因客戶信用不良而被拒的訂單,平均應少於十分之一。若連續拒絕超過五張訂單,這可能表示某個外部服務或商業邏輯出了問題。管理主控台可以把這五張訂單 e-mail 給管理員,讓他快速看一眼、判斷這些拒絕是否合理。

圖 1-27:插入 Smart Proxy 追蹤回應時間

圖 1-28:注入測試訊息以驗證結果正確性

小結#

我們走過了一個相當完整的整合情境,途中用上多種整合策略——檔案傳輸、共享資料庫與非同步訊息——並對訊息做了路由、分割與聚合,也加上了監控方案是否正確運作的功能。

本例的需求雖然刻意簡化,但我們必須考量的議題與設計取捨都非常真實。方案圖與描述凸顯了一件事:我們能用一種與廠商、技術都無關的語言描述解法,而且比高層次的循序圖精確得多。

本章的整合情境主要聚焦在如何連接既有應用程式。至於如何在自製應用程式內部發布與消費訊息,請看後續兩個間奏章節的範例。

本書其餘部分會針對設計中用到的每個模式給出詳細描述與程式碼範例。模式依主要意圖分類為:基礎模式、通道模式、訊息模式、路由模式、轉換模式、端點模式與系統管理模式——這種編排讓你既能依序讀完,也能當作參考書查閱個別模式。