脈絡:前面的模式說明了如何建構訊息、如何把它路由到正確的目的地。但在許多情況下,企業整合方案是在既有應用程式之間路由訊息——遺留系統、套裝應用、自製應用,或由外部夥伴營運的系統。

這些應用程式各自圍繞著專有的資料模型打造:

  • 每個應用程式對「客戶」這個實體的認知都略有不同——定義客戶的屬性是哪些、客戶又與哪些其他實體有關聯
  • 會計系統可能更在意客戶的稅籍編號,而 CRM 系統存的是電話與地址
  • 應用程式底層的資料模型通常直接決定了實體資料庫 schema、介面檔案格式或 API——而這些正是整合方案必須介接的東西

結果是:應用程式期待收到的訊息,長得像它自己的內部資料格式。

除了各應用程式的專有模型與格式外,整合方案往往還要與力求獨立於特定應用的標準資料格式打交道,例如 RosettaNet、ebXML、OAGIS 以及各種產業專屬聯盟所定義的協定。許多情況下,整合方案必須用「官方」格式與外部各方溝通,而內部系統卻建立在專有格式上。

為什麼不乾脆統一格式#

如果能修改所有應用程式改用共同資料格式,就不必轉換訊息了。但這在多個層面上都很困難:

  • 改資料格式風險高、難度大,而且需要大量修改內在的業務功能。對多數遺留應用程式而言,改資料格式在經濟上根本不可行。

還記得 Y2K 改造工程嗎?那次變更的範圍不過就是一個欄位的長度而已。

  • 就算欄位名稱與資料型別統一了,實體表示法仍可能天差地遠——一個用 XML 文件,另一個用 COBOL copybook。
  • 調整一方去遷就另一方,反而把兩者綁得更緊。 企業整合的關鍵架構原則之一是鬆散耦合(見 Canonical Data Model),而修改 A 去配合 B 的資料格式,會讓兩者直接相依於彼此的內部表示法——這就沒辦法在不影響另一方的前提下替換或修改其中一方,而這種需求在企業整合中相當常見。
  • 把格式轉換直接做進 Message Endpoint? 這樣所有應用程式都會以共同格式收發訊息,而非各自的內部格式。但這需要存取端點程式碼,對套裝應用而言通常辦不到;而且把格式轉換寫死在端點裡,也會減少程式碼重用的機會。

解法#

Message Translator 是 [GoF] 中 Adapter 模式在訊息傳遞領域的對應物——轉接器把一個元件的介面轉成另一種介面,好讓它能在不同情境下被使用。

圖 3-5:Message Translator 解法示意

轉換的四個層次#

訊息轉換可能發生在數個不同層次。有時資料元素名稱與型別相同,只是表示法不同(XML 檔 vs. 逗號分隔值 vs. 固定長度欄位);有時全都用 XML,卻採用不同的標籤名稱。

借用 OSI 參考模型的精神,可以把轉換分成四層:

層次處理的東西轉換需求(範例)工具/技術
資料結構(應用層)實體、關聯、基數把多對多關係壓成聚合結構對應模式、自製程式碼
資料型別欄位名稱、資料型別、值域、限制、代碼值郵遞區號從數值轉字串;把 first name 與 last name 串成單一 name 欄位;把美國州名換成兩字母代碼EAI 視覺化轉換編輯器、XSL、資料庫查表、自製程式碼
資料表示資料格式(XML、名值對、固定長度欄位、EAI 廠商格式)、字元集(ASCII、Unicode、EBCDIC)、加密/壓縮解析資料表示並以另一種格式輸出;視需要加解密XML 剖析器、EAI 剖析/輸出工具、自製 API
傳輸通訊協定:TCP/IP sockets、http、SOAP、JMS、TIBCO RendezVous在不影響訊息內容的前提下跨協定搬運資料Channel Adapter、EAI 轉接器

逐層說明#

  • 傳輸層位於堆疊底部,負責在不同系統之間完整且可靠地傳輸資料,處理跨網段的封包遺失與其他網路錯誤。有些 EAI 廠商提供自家傳輸協定(如 TIBCO RendezVous),其他整合技術則沿用 TCP/IP 協定(如 SOAP)。不同傳輸層之間的轉換由 Channel Adapter 提供。
  • 資料表示層也稱「語法層」,定義被傳輸資料的表示法。之所以需要這層轉換,是因為傳輸層只能搬運字元或位元組流,複雜資料結構必須先轉成字串。常見格式包含 XML、固定長度欄位(如 EDI 紀錄)或專有格式;資料也常被壓縮或加密、帶有檢查碼或數位憑證。要介接表示法不同的系統,資料可能得先解密、解壓縮、剖析,再輸出成新格式,可能還要重新壓縮與加密。
  • 資料型別層定義應用(領域)模型所依據的資料型別。這裡處理的是:日期欄位要存成字串還是原生日期結構?日期是否帶時間?基於哪個時區?Postal Code 只放美國 ZIP,還是也能放加拿大郵遞區號?若是美國 ZIP,要不要含 ZIP+4?是否必填?存成一欄還是兩欄?這些問題通常在**資料字典(Data Dictionary)**中處理。

資料型別的議題遠不只「這欄是字串還是整數」。

想像依區域組織的銷售資料:某部門的應用程式把國家分成四區——西部、中部、南部、東部,以 WCSE 標示;另一個部門卻把太平洋區與山區分開、東北與東南也分開,每區以唯一的兩位數字標示。那麼字母 E 對應到哪個數字?

  • 資料結構層在應用領域模型的層次描述資料,因此也叫應用層。它定義應用程式處理的邏輯實體(Customer、Address、Account)以及它們之間的關係:一位客戶能有多個帳戶嗎?能有多個地址嗎?客戶之間能共用地址嗎?多位客戶能共用一個帳戶嗎?地址屬於帳戶還是客戶?——這是實體關係圖與類別圖的領域。

解耦的層級#

整合中的許多設計取捨,都由「讓元件或應用程式解耦」這個需求所驅動。解耦是管理變更的必要工具:整合連接的是既有應用程式,而且必須容納它們的變化。

  • Message Channel 讓應用程式不必知道彼此的位置。
  • Message Router 甚至讓應用程式不必對共同的路由達成共識。
  • 但只要它們仍相依於彼此的資料格式,這種解耦所能達成的獨立性就很有限——Message Translator 能移除這一層相依。

串接轉換#

許多商業情境需要跨多層的轉換。

例如一筆以固定格式檔案呈現的 EDI 850 採購訂單,必須轉成 XML 文件、透過 http 送到訂單管理系統,而該系統對 Order 物件的定義又不一樣。這個轉換橫跨全部四層:傳輸從檔案變成 HTTP、資料表示從固定格式變成 XML、資料型別與結構都得轉換以符合訂單管理系統定義的 Order 物件。

分層模型的美妙之處在於:我們可以只處理某一層而不管底下各層,因而能選擇在不同的抽象層次上工作。

用 Pipes and Filters 串接多個 Message Translator,好處是每一層各一個轉譯器,這些元件都能在其他情境中重用——例如 Channel Adapter 與 EDI-to-XML 轉譯器可以通用到能處理任何進站的 EDI 文件。

這種作法也讓個別層次可以互換:你可以沿用同一套結構轉換機制,只把「資料表示轉換」那一環抽換掉,就從輸出固定格式改成輸出逗號分隔檔。

相關的特化與變體#

Message Translator 有許多特化與變體:

  • Content Enricher——擴充訊息內的資訊
  • Content Filter——移除訊息內的資訊
  • Claim Check——移除資訊,但存起來供日後取回
  • Normalizer——把多種不同的訊息格式轉成一致的格式
  • Canonical Data Model——說明如何運用多個 Message Translator 達成資料格式的解耦
  • Messaging Bridge——透過連接多套訊息系統,執行傳輸層的轉換

上述每個模式內部都可能發生複雜的結構轉換,例如把多對多關係對應成一對一關係。

範例:用 XSL 做結構轉換

轉換的需求如此普遍,以致 W3C 定義了轉換 XML 文件的標準語言 XSL。XSL 的一部分是 XSLT——一種以規則為基礎、把 XML 文件轉成另一種格式的語言。

假設有一份進站 XML 文件要送給會計系統。若兩邊都用 XML,資料表示層就相同了,我們只需處理欄位名稱、資料型別與結構的差異。進站文件長這樣:

<data>
   <customer>
         <firstname>Joe</firstname>
         <lastname>Doe</lastname>
         <address type="primary">
            <ref id="55355"/>
         </address>
         <address type="secondary">
            <ref id="77889"/>
         </address>
   </customer>
   <address id="55355">
         <street>123 Main</street>
         <city>San Francisco</city>
         <state>CA</state>
         <postalcode>94123</postalcode>
         <country>USA</country>
         <phone type="cell">
            <area>415</area>
            <prefix>555</prefix>
            <number>1234</number>
         </phone>
         <phone type="home">
            <area>415</area>
            <prefix>555</prefix>
            <number>5678</number>
         </phone>
   </address>
   <address id="77889">
         <company>ThoughtWorks</company>
         <street>410 Townsend</street>
         <city>San Francisco</city>
         <state>CA</state>
         <postalcode>94107</postalcode>
         <country>USA</country>
   </address>
</data>

這份文件裝的是客戶資料:每位客戶可關聯多個地址,每個地址可含多個電話號碼。XML 把地址表示成獨立實體,因此多位客戶可以共用同一個地址。

會計系統需要的表示法則是(如果你覺得德文標籤名很牽強,別忘了最熱門的企業軟體之一正是以德文欄位名聞名):

<Kunde>
   <Name>Joe Doe</Name>
   <Adresse>
       <Strasse>123 Main</Strasse>
       <Ort>San Francisco</Ort>
       <Telefon>415-555-1234</Telefon>
   </Adresse>
</Kunde>

結果文件的結構簡單得多:標籤名稱不同、部分欄位被合併;而且只放得下一個地址與一組電話,因此必須依業務規則從原文件中挑一個。以下 XSLT 程式完成這項轉換:

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
   <xsl:output method="xml" indent="yes"/>
   <xsl:key name="addrlookup" match="/data/address" use="@id"/>
   <xsl:template match="data">
       <xsl:apply-templates select="customer"/>
   </xsl:template>
   <xsl:template match="customer">
       <Kunde>
           <Name>
               <xsl:value-of select="concat(firstname, ' ', lastname)"/>
           </Name>
           <Adresse>
               <xsl:variable name="id" select="./address[@type='primary']/ref/@id"/>
               <xsl:call-template name="getaddr">
                    <xsl:with-param name="addr" select="key('addrlookup', $id)"/>
               </xsl:call-template>
           </Adresse>
       </Kunde>
   </xsl:template>
   <xsl:template name="getaddr">
       <xsl:param name="addr"/>
       <Strasse>
            <xsl:value-of select="$addr/street"/>
        </Strasse>
        <Ort>
            <xsl:value-of select="$addr/city"/>
        </Ort>
        <Telefon>
            <xsl:choose>
                 <xsl:when test="$addr/phone[@type='cell']">
                    <xsl:apply-templates select="$addr/phone[@type='cell']" mode="getphone"/>
                 </xsl:when>
                 <xsl:otherwise>
                    <xsl:apply-templates select="$addr/phone[@type='home']" mode="getphone"/>
                 </xsl:otherwise>
            </xsl:choose>
        </Telefon>
    </xsl:template>
    <xsl:template match="phone" mode="getphone">
        <xsl:value-of select="concat(area, '-', prefix, '-', number)"/>
    </xsl:template>
    <xsl:template match="*"/>
</xsl:stylesheet>

XSL 建立在模式比對上,對習慣程序式程式設計的人來說可能有點難讀。簡單說:每當進站文件中的元素符合 match 屬性所指定的運算式,對應的 <xsl:template> 就被呼叫。例如 <xsl:template match="customer"> 會讓後續幾行對來源文件中的每個 <customer> 元素各執行一次;接著把 first 與 last name 串起來輸出到 <Name>

取地址稍微麻煩些:XSL 先查出正確的 <address> 元素實例,再呼叫「副程式」getaddrgetaddr 從該 <address> 抽出地址與電話——有手機號碼就用手機,否則用家用電話

範例:視覺化轉換工具

圖 3-6:以拖放方式建立轉換(BizTalk Mapper)

如果你覺得 XSL 有點難懂,你並不孤單。因此多數整合廠商提供視覺化轉換編輯器:畫面左右兩側分別顯示兩種文件格式的結構,使用者在兩側之間拖放來建立元素對應,這比手寫 XSL 簡單得多。也有廠商完全專攻轉換工具,例如 Contivo, Inc.。

整合進 Visual Studio 的 Microsoft BizTalk Mapper 就是一例:圖表比 XSL 腳本更清楚地呈現個別元素之間的對應,但部分細節(例如地址是怎麼挑出來的)被藏在 functoid 圖示底下

能拖放做轉換,大幅縮短了開發 Message Translator 的學習曲線。但一如既往,視覺化工具在除錯或建構複雜方案時也可能變成負擔——因此許多工具讓你能在 XSL 與視覺化工具之間來回切換。