🎯 什麼情境該想到我
當你「串接的兩個系統各用不同的資料格式或結構,需要在中間把訊息從一種格式轉成另一種」的時候。
⚙️ 怎麼用(步驟 / 公式)
意圖:用一個中介的轉換元件,在使用不同資料格式的系統之間翻譯訊息的格式與結構。
做法 / 結構要點:
- 轉換器收到來源格式的訊息,依對映規則轉出目標格式的訊息。
- 可涉及不同層次:資料型別、資料結構、資料表示(編碼)、傳輸方式。
- 讓兩端各自維持自己的格式,不必為了對方而改造,透過轉換器解耦。
★★ 轉換該放在哪一端:這是最關鍵的決定
① 生產者轉成「大家都懂的格式」 → ★ 生產者被所有消費者綁住
② 消費者各自轉成自己要的格式 → ★ 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 測試。
🔗 相關工具
- 訊息路由器 Message Router(互補:轉換器改內容、路由器決定去向)
- 管道與過濾器 Pipes and Filters(轉換器是其中一種過濾器)
- 通道配接器 Channel Adapter(常在配接外部系統時一併做格式轉換)
- 企業整合模式
- 內容擴充器、內容過濾器、正規化器、標準資料模型 —— 轉換家族
- 信封包裝器 —— 只動外層包裝
- 訊息對應器 —— 端點內部的領域物件對應
- 防腐層 —— 隔離外部模型的轉換層
- 拆分階段 —— 讓轉換成為可測的純函式
- 工具-資料編碼與演進 —— 編碼格式與相容性
- 工具-Go表格測試與子測試 —— 轉換的邊界測試