🎯 什麼情境該想到我

當你「發請求時要告訴接收方『回覆該送到哪一條通道』」的時候。

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

意圖:讓回覆者知道要把回覆送往哪個通道,而不是寫死回哪。

做法要點:

  • 在請求訊息的標頭帶上「回覆通道的位址」(Return Address)。
  • 接收端讀取此位址,把回覆送往指定通道。
  • 讓請求-回覆中不同請求者能各自指定專屬的回覆通道。

★★ 核心紀律:回覆位址由「請求者」決定,寫在請求裡

// ❌ 接收端寫死回覆去哪 —— ★ 加一個新的請求者就要改接收端
func handle(req Request) { publish(ctx, "svc-a.replies", reply) }
 
// ✅ 請求者說了算
type Request struct {
    RequestID string `json:"request_id"`
    ReplyTo   string `json:"reply_to"`     // ★ 由請求者填
}
func handle(req Request) { publish(ctx, req.ReplyTo, reply) }

★ 這就是「回覆位址」的全部價值:讓接收端不必認識任何請求者。
同一個服務可以同時服務十個請求者,而它的程式碼裡沒有任何一個的名字。

★ 各家 broker 的原生支援

broker機制
AMQP/RabbitMQ★ reply-to 是 AMQP 協定的標準 property,不必自己定義
JMSJMSReplyTo(EIP 書寫的就是這個)
Kafka★ 沒有原生欄位——自己在 headers 放 reply-to
SQS沒有原生——放 message attribute
NATS★ 有內建的 request-reply(nc.Request() 自動處理回覆主題)

★ NATS 是唯一把請求-回覆做成一級功能的:nc.Request(subj, data, timeout)
直接給你同步語義,回覆主題與關聯都由 client 處理掉。

★★ 安全:這是最容易被忽略的攻擊面

// ❌ 盲目信任請求裡的 ReplyTo
publish(ctx, req.ReplyTo, reply)
// ★★ 惡意請求者可以把 ReplyTo 設成任何通道:
//    ① 導到別人的回覆通道 → ★ 污染他人資料
//    ② 導到高流量通道 → ★ 用你的服務當放大器打別人(SSRF 的訊息版)
//    ③ 導到不存在的通道 → RabbitMQ 靜默丟棄;Kafka 可能自動建 topic → ★ topic 爆增
 
// ✅ 白名單驗證
var allowedReplyChannels = map[string]bool{
    "svc-a.replies": true,
    "svc-b.replies": true,
}
if !allowedReplyChannels[req.ReplyTo] {
    return publishTo(invalidChannel, req, errors.New("reply_to 不在白名單"))
}

★ 或更穩健:不要讓請求者指定完整通道名,只讓它指定一個「識別碼」,
由接收端自己對應到通道名。

⚠️ Kafka 的 auto.create.topics.enable 若為開啟,這個漏洞會直接造成 topic 爆增。
生產環境應該關掉它。

★ 回覆位址不見/無效時要怎麼辦

switch {
case req.ReplyTo == "":
    // ★ 判斷:這個請求「本來就不需要回覆」,還是「請求者忘了填」?
    //   → 看訊息型別。命令通常需要回覆;fire-and-forget 不需要
    //   → ★ 曖昧的話就當成錯誤,送 [[無效訊息通道]],不要靜默不回
case !allowed(req.ReplyTo):
    publishTo(invalidChannel, req, errInvalidReplyTo)
default:
    publish(ctx, req.ReplyTo, reply)   // ★ 這一步失敗也要處理:回覆發不出去 ≠ 工作沒做完
}

★★ 最容易漏的一點:「工作做完了但回覆發不出去」。
如果你在這時 nack 訊息 → 整個工作會被重做(可能重複扣款)。
→ ★ 正確做法:工作與回覆分開記錄。 工作完成就記在資料庫,
回覆失敗單獨重試(這其實是 工具-Transactional-Outbox-Pattern 的同一個問題)。

★ 兄弟辨析

差別
請求-回覆★ 整體模式;回覆位址是它的「去哪」那一半
關聯識別碼★ 「這則回覆對應哪個請求」——「哪一個」那一半
路由單★ 訊息帶著整條路徑,不只是回覆位址
訊息閘道請求者端封裝「發請求 + 等回覆」的地方

🧪 我實際套用的紀錄

  • (待填)

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

  • 位址放在訊息標頭,而非本體。

  • 需配合關聯識別碼,才能把送回來的回覆對應回原請求。

  • 要確保指定的回覆通道存在、且請求者正在監聽。

  • ★★ 不可盲目信任請求裡的 ReplyTo。可被用來污染他人通道、當放大器、或造成 topic 爆增。要白名單驗證。

  • ★ Kafka 生產環境應關掉 auto.create.topics.enable,否則惡意的 reply-to 會製造大量 topic。

  • ★★ 「工作做完但回覆發不出去」不可以 nack,否則整個工作會重做(可能重複扣款)。工作與回覆要分開記錄。

  • ★ ReplyTo 為空時不要靜默不回。判斷是「本來不需要」還是「忘了填」,曖昧就送無效訊息通道。

  • ★ 回覆位址由請求者決定,接收端不該寫死——否則加一個請求者就要改接收端。

  • Kafka/SQS 沒有原生 reply-to 欄位,要自己在 headers/attributes 定義並統一規範。

  • 更穩健的做法是讓請求者只指定識別碼,由接收端對應到真正的通道名。

🔗 相關工具