🎯 什麼情境該想到我

當你「同一種訊息卻由不同來源以各自不同的格式送來,你想先把它們統一成一個共通格式再處理」的時候。

⚙️ 怎麼用(步驟 / 公式)

意圖:把語意相同但格式不同的多種訊息,各自轉換成一個共同的標準格式。

做法 / 結構要點:

  • 用類型判斷(如訊息型別、來源)辨識進來的訊息屬於哪種格式。
  • 為每種來源格式準備一個對應的轉換器。
  • 透過路由把訊息導到對應轉換器,輸出統一格式。

★ 結構:路由器 + N 個轉換器 + 一個共同輸出

供應商A 的 XML ──┐
供應商B 的 CSV ──┼──[[訊息路由器]]──▶ 各自的 [[訊息轉換器]] ──▶ 統一格式 ──▶ 下游
供應商C 的 JSON ─┘        ↑ 依來源/格式分派        ↑ 每種來源一個,互不影響

★ 這個模式的價值在於「隔離」:新增一個供應商 = 加一個轉換器 + 一條路由規則,
既有的 N 個轉換器一行都不用改。→ 這就是 工具-開閉原則 在整合層的體現。

★ Go 的實作:分派表 + 純函式轉換器

type Normalizer func([]byte) (CanonicalOrder, error)
 
var normalizers = map[string]Normalizer{
    "supplier-a": normalizeSupplierA,     // ★ 每個都是純函式 → 可以用表格測試窮舉
    "supplier-b": normalizeSupplierB,
}
 
func normalize(source string, raw []byte) (CanonicalOrder, error) {
    n, ok := normalizers[source]
    if !ok {
        // ★★ 未知來源絕對不要靜默丟棄
        return CanonicalOrder{}, fmt.Errorf("%w: 未知來源 %q", ErrInvalidMessage, source)
    }
    return n(raw)
}

★★ 「怎麼知道這是哪個來源」有三種方式,可靠度差很多:

① ★ 通道/佇列決定(每個供應商一條專用通道)—— ★ 最可靠,推薦
② ★ 標頭欄位(source: "supplier-a")—— 可靠,但要防偽造
③ ★★ 猜格式(sniffing:開頭是 < 就是 XML)—— ★ 最脆弱,但整合遺留系統時常常只能這樣

★ ① 優先。它讓「這是誰送來的」變成基礎設施保證,而不是資料內容的解讀。

★★ 這個模式最大的風險:共同格式會被「最後一個供應商」綁架

★ 典型的腐敗過程:
第 1 個供應商 → CanonicalOrder 有 8 個欄位,乾淨
第 5 個供應商 → 它有一個特殊的「分期付款期數」→ 加一個欄位(其他 4 個都是 null)
第 12 個供應商 → 加三個欄位
→ ★★ CanonicalOrder 變成所有供應商欄位的聯集,而每個實例只有 20% 的欄位有值
→ ★ 下游必須判斷每個欄位「這個來源有沒有給」⇒ 正規化的價值消失了

★★ 對策:共同格式只包含「所有來源都有、且下游都需要」的欄位。
來源特有的資料放一個明確的 SourceSpecific json.RawMessage 欄位,
並且下游原則上不該讀它——要讀就表示那個下游其實需要知道來源,
那就別走正規化這條路。

★ 這是 標準資料模型 的同一個陷阱,規模小一號但一樣致命。

★★ 資訊損失是必然的,要明確決定丟什麼

type CanonicalOrder struct {
    OrderID  string
    Lines    []Line
    Total    Money
    // ★ 明確記錄來源與原始資料位置,讓「需要時能回去查」
    Source      string          `json:"source"`
    RawRef      string          `json:"raw_ref"`       // ★★ 原始訊息的儲存位置
    Dropped     []string        `json:"dropped,omitempty"` // ★ 明確記錄丟了哪些欄位
}

★★ RawRef 是最有價值的一欄:把原始訊息落地保存(訊息存放區/寄物單),
正規化後的訊息只帶位址。
→ 爭議發生時(「我明明有送這個欄位」)你能拿出原始資料,這在 B2B 整合裡是必需的。

★ 兄弟辨析

差別
訊息轉換器★ 單一轉換;正規化器是「路由 + N 個轉換」的組合
標準資料模型★ 正規化器的輸出格式如果推廣到整個企業,就變成它——★ 注意那一頁的代價
格式指示器判斷來源/版本的依據之一
防腐層★ 同一想法在 DDD 的說法(隔離外部模型)
配接器模式行程內的同一想法

🧪 我實際套用的紀錄

  • (待填)

⚠️ 注意 / 什麼時候不適用

  • 每多一種來源格式,就要多一個轉換器。

  • 常與標準資料模型搭配:轉成的「統一格式」即中立模型。

  • 辨識格式的規則要可靠,誤判會轉錯。

  • ★★ 共同格式會被「最後一個供應商」綁架,變成所有來源欄位的聯集。只放「所有來源都有且下游都需要」的欄位。

  • ★★ 來源特有資料放明確的 SourceSpecific,而下游原則上不該讀它。要讀就表示它其實需要知道來源。

  • ★★ 要保存原始訊息並在正規化結果裡帶 RawRef。B2B 整合的爭議處理需要它。

  • ★ 「這是哪個來源」優先用通道決定,不要靠猜格式或信任資料裡的欄位。

  • ★★ 未知來源絕對不要靜默丟棄,送 無效訊息通道 並計數。

  • ★ 資訊損失是必然的。要明確記錄丟了哪些欄位,而不是悄悄丟。

  • 每個轉換器要是純函式,才能用表格測試覆蓋每個來源的邊界。

  • 這是 標準資料模型 的小一號版本,同樣的陷阱——先讀那一頁。

🔗 相關工具