🎯 什麼情境該想到我
當你「想臨時把訊息導去經過額外的驗證、測試或除錯步驟,之後又能切回正常路徑」的時候。
⚙️ 怎麼用(步驟 / 公式)
意圖:用一個可由控制匯流排切換的路由,讓訊息在需要時繞道經過額外處理(驗證 / 測試 / 除錯),平時則走直達路徑。
做法 / 結構要點:
- 在路徑上放一個可切換的路由器(受控制匯流排控制)。
- 開關開啟時,訊息被導去經過額外的中間步驟再回到主流程。
- 關閉時訊息直達,不受影響。
★★ 這個模式的真正價值:它讓「觀察」不影響「正確性」
正常: producer ──▶ [主通道] ──▶ consumer
繞道: producer ──▶ [驗證/記錄/轉換] ──▶ [主通道] ──▶ consumer
↑ ★ 可以隨時插入、隨時移除,不必改兩端
★ 和 竊聽器 的關鍵差別:
★ 竊聽器:複製一份出去(★ 主流程不受影響,是「旁觀」)
★ 繞道 :★★ 訊息真的改走另一條路(★ 主流程被改變,是「介入」)
→ 所以繞道能做竊聽器做不到的事(阻擋、延遲、修改、重排),
代價是它在主路徑上,它掛了訊息就停了。
★ 四個真實用途
| 用途 | 說明 |
|---|---|
| ① ★ 除錯期的檢查點 | 把訊息導去一個會詳細記錄的元件,確認內容正確後再放行 |
| ② ★★ 遷移期的雙寫驗證 | 導到新舊兩套實作,★ 比對結果是否一致,不一致就告警——新系統不上線就能驗證 |
| ③ ★ 灰度/金絲雀 | 1% 的訊息繞道到新版消費者 |
| ④ ★★ 取代「過濾器」的丟棄 | 不符條件的訊息不要丟掉,導去一條 filtered.* 通道(見 訊息過濾器) |
★★ ② 是最有價值的一個,而且常被忽略:
遷移訂單處理邏輯 → 舊系統照常處理(真的生效)
→ 同時把訊息繞道給新系統(★ 只計算,不產生副作用)
→ ★★ 比對兩者的輸出,累積幾週的一致性資料
→ ★ 這讓「換掉核心邏輯」變成可以量化風險的事
★ 這其實就是 工具-特徵測試 的線上版本,也和 A-B測試與假設驅動開發 同源。
★★ Go 的實作要點:繞道必須可以「零風險關掉」
// ★ 繞道一定要是可動態開關的 —— 否則它就是一個永久的額外風險
type Detour struct {
enabled atomic.Bool // ★ 由 [[控制匯流排]] 或 feature flag 控制
shadow Sink // 影子目的地
main Sink
}
func (d *Detour) Handle(ctx context.Context, msg Message) error {
if d.enabled.Load() {
// ★★ 影子路徑的失敗絕對不能影響主路徑
go func() {
sctx, cancel := context.WithTimeout(context.WithoutCancel(ctx), 2*time.Second)
defer cancel()
if err := d.shadow.Send(sctx, msg); err != nil {
metrics.ShadowErrors.Inc() // ★ 只記錄,不回傳錯誤
}
}()
}
return d.main.Send(ctx, msg) // ★ 主路徑的行為完全不變
}★★ 三個關鍵:
- 影子路徑的失敗只記錄,不回傳錯誤 —— 否則繞道會讓你的系統變得更不可靠。
- ★
context.WithoutCancel(Go 1.21+)—— 主路徑完成後 ctx 被取消,
影子路徑不該跟著被砍掉。 - 獨立的超時 —— 影子系統慢不該拖慢主路徑。
⚠️ 但 go func() 的版本有一個代價:影子路徑「可能漏訊息」(行程重啟時)。
★ 需要完整比對時,要改成「同步發到一條真的影子通道」,讓它有自己的可靠性保證。
★★ 最重要的警告:繞道要有退場日期
「我們先加一個繞道來觀察」
→ ★ 半年後它還在,而且:
① 主路徑上多一跳(延遲、額外的失敗模式)
② ★★ 沒人記得它為什麼存在,也沒人敢拿掉
③ ★ 它產生的資料沒人看(★ 比不存在更糟——你以為有在監控)
→ ★ 對策:加繞道時就開一張帶到期日的 ticket
★ 兄弟辨析
| 差別 | |
|---|---|
| 竊聽器 | ★ 只複製,不介入主流程;★ 更安全但能做的事更少 |
| 訊息過濾器 | ★ 丟掉訊息;繞道是「導去別處」——★ 通常繞道是更好的選擇 |
| 智慧代理 | ★ 攔在中間並記錄請求-回覆的配對 |
| 訊息歷史 | 記錄走過的路徑,不改變路徑 |
| 工具-特徵測試 | ★ 雙寫驗證是它的線上版本 |
🧪 我實際套用的紀錄
- (待填)
⚠️ 注意 / 什麼時候不適用
-
繞道邏輯常駐但多半關閉,要確保關閉時開銷極低。
-
切換要能動態、即時(透過控制匯流排)。
-
繞道步驟本身別改壞訊息內容(除非刻意)。
-
★★ 繞道在主路徑上,它掛了訊息就停了。 竊聽器只是旁觀,更安全。
-
★★ 影子路徑的失敗只能記錄,不能回傳錯誤,否則繞道讓系統變得更不可靠。
-
★ 用
context.WithoutCancel(Go 1.21+),否則主路徑完成後影子路徑被砍掉。 -
★★ 繞道要有退場日期。半年後沒人記得它為什麼存在、也沒人敢拿掉,而它產生的資料沒人看——比不存在更糟。
-
★ 一定要可動態開關(feature flag/控制匯流排),否則它是一個永久的額外風險。
-
★
go func()版的影子路徑可能漏訊息(行程重啟時)。需要完整比對就改成真的影子通道。 -
★ 雙寫驗證(導到新舊兩套比對結果)是這個模式最有價值的用法,常被忽略。
-
不要用繞道做「暫時的業務邏輯」。它會變成隱形的、不在主程式碼裡的規則。
🔗 相關工具
- 控制匯流排 Control Bus(同家族,負責切換繞道的開關)
- 竊聽器 Wire Tap(同家族,另一種觀察訊息的手法)
- 企業整合模式
- 竊聽器 —— 只複製不介入
- 訊息過濾器 —— ★ 導流通常勝過丟棄
- 智慧代理 —— 記錄請求-回覆配對
- 訊息歷史 —— 記錄路徑
- 控制匯流排 —— 動態開關繞道
- 工具-特徵測試 —— 雙寫驗證的離線版本
- A-B測試與假設驅動開發 —— 灰度與量化風險