🎯 什麼情境該想到我

當你「串接的兩個系統各用不同的資料格式或結構,需要在中間把訊息從一種格式轉成另一種」的時候。

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

意圖:用一個中介的轉換元件,在使用不同資料格式的系統之間翻譯訊息的格式與結構。

做法 / 結構要點:

  • 轉換器收到來源格式的訊息,依對映規則轉出目標格式的訊息。
  • 可涉及不同層次:資料型別、資料結構、資料表示(編碼)、傳輸方式。
  • 讓兩端各自維持自己的格式,不必為了對方而改造,透過轉換器解耦。

★★ 轉換該放在哪一端:這是最關鍵的決定

① 生產者轉成「大家都懂的格式」  → ★ 生產者被所有消費者綁住
② 消費者各自轉成自己要的格式    → ★ N 個消費者 = N 份轉換邏輯(但各自獨立演進)
③ ★ 中間放一個轉換器服務        → 多一跳、多一個部署單元
④ ★★ 共用「標準資料模型」       → 見 [[標準資料模型]],代價最容易被低估

★ 判準:轉換邏輯的變動是誰驅動的?
消費者的需求驅動(B 服務想多一個欄位)→ ② 放消費者端。這是預設答案。
生產者的格式很爛且不會改(第三方、遺留系統)→ ③ 一個轉換器/防腐層。

⚠️ 最常見的錯誤是選 ①:為了「方便」讓生產者輸出一個服務所有消費者的格式,
結果生產者的 schema 變成公共契約,任何消費者的新需求都要改生產者。

★ Go 的轉換實作:讓「轉換」成為可測的純函式

// ★ 轉換就是純函式:吃一個型別,吐另一個型別。不碰 I/O
func toFulfilmentOrder(e OrderPlacedV1) (FulfilmentOrder, error) {
    if e.ShippingAddress == nil {
        return FulfilmentOrder{}, fmt.Errorf("order %s: missing shipping address", e.OrderID)
    }
    return FulfilmentOrder{
        OrderID: e.OrderID,
        Lines:   mapLines(e.Items),
        Address: *e.ShippingAddress,
    }, nil
}

★ 這是 拆分階段 在訊息系統裡的應用:
「解析 → 轉換 → 輸出」三段分開,中間的轉換是純函式,可以直接用表格測試覆蓋所有邊界
(見 工具-Go表格測試與子測試)。

★★ 轉換最容易出錯的五個地方(每一個都造成過真實事故)

// ① ★ 時區與時間格式
//    "2026-09-25 10:00" 沒有時區 → 落地時變成 UTC 還是本地時間?
//    ★ 一律用 RFC3339 帶 offset,內部一律 UTC
 
// ② ★★ 金額精度
//    float64 → 0.1+0.2 != 0.3;JSON 的 number 在 JS 端是 float64
//    ★ 一律用整數最小單位(分),或字串
 
// ③ ★ 大整數在 JSON 裡失真
//    Go 的 int64 → JavaScript 的 Number 只有 53 bit 安全整數
//    ★ 雪花 ID/訂單號要用字串傳
 
// ④ ★ 「零值」與「沒給」不可區分
//    Go 的 struct 零值:quantity: 0 是「零件」還是「沒填」?
//    ★ 需要區分時用指標或 wrapper(見 [[工具-型別推導還是解耦]])
 
// ⑤ ★ 列舉值的對應不完整
//    上游加了一個新的 status,你的 switch 沒有 default → 靜默變成零值
//    ★ 一定要有 default 並回錯(Go 沒有窮盡檢查)

★ 分層:不要一次跨太多層

EIP 把轉換分成四層,這個分層在實務上很有用,因為它告訴你「這次的變更會波及多遠」:

① 傳輸層   HTTP ↔ AMQP ↔ Kafka        → [[通道配接器]]、[[訊息橋接]]
② 資料表示 JSON ↔ protobuf ↔ Avro      → 純編碼轉換,無語義變化
③ 資料型別 string "100" ↔ int64 100    → ★ 這一層最容易靜默失真
④ ★ 資料結構 一則訂單 ↔ 三則出貨單      → ★ 有語義,需要業務知識

★ 第 ④ 層才需要業務知識,前三層應該由函式庫/codegen 處理。
如果你在手寫第 ②③ 層的轉換,通常是編碼選錯了(見 工具-資料編碼與演進)。

★ 兄弟辨析

差別
內容擴充器★ 轉換 + 去外部查資料補上(有 I/O,會失敗,要考慮延遲)
內容過濾器只移除欄位(縮小訊息、去敏感資料)
正規化器★ 多種不同格式 → 同一種格式(配路由器,每種來源一個轉換器)
訊息對應器領域物件 ↔ 訊息的對應(★ 端點內部的事,不是獨立元件)
信封包裝器只加/脫外層包裝,不動內容

🧪 我實際套用的紀錄

  • (待填)

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

  • 轉換只改格式、不改流向;決定「送去哪」是路由器的事。

  • 多對多的系統若各自互轉會爆炸,通常搭配一個共同的標準格式(正規化)較好維護。

  • ★★ 預設把轉換放在消費者端。讓生產者輸出「大家都懂的格式」會把它的 schema 變成公共契約。

  • ★ 時區、金額精度、大整數、零值歧義、列舉遺漏——這五個是轉換的頭號事故來源。

  • ★ Go 的 int64 傳到 JavaScript 會失真(只有 53 bit 安全整數)。ID 用字串。

  • ★ 列舉對應一定要有 default 並回錯。Go 沒有窮盡檢查,上游加一個值就靜默變零值。

  • ★ 轉換要是純函式,才能用表格測試覆蓋邊界。混進 I/O 就變成 內容擴充器。

  • 手寫「資料表示/資料型別」層的轉換通常代表編碼選錯了。那兩層該由 codegen 處理。

  • 轉換器是額外一跳。能在消費者端用一個函式解決,就不要部署一個服務。

  • 雙向轉換不對稱是常見 bug:A→B→A 拿不回原值。有雙向需求就寫 round-trip 測試。

🔗 相關工具