Topic Exchange
routing ด้วย pattern
หัวข้อที่มีชื่อว่า “routing ด้วย pattern”topic exchange match routing key ของ message กับ pattern ในแต่ละ binding เป็น exchange type ที่ยืดหยุ่นที่สุด: direct คือ “เท่ากัน”, fanout คือ “ทั้งหมด” และ topic คือ “match รูปทรงนี้” topic จึงเปิดให้ subscriber บอกว่า “ฉันอยากได้ event แบบเหล่านี้” โดยที่ producer ไม่ต้องรู้ว่าใครฟังอยู่
routing key มีโครงสร้างเป็น word คั่นด้วยจุด: order.created, payment.failed.th, sensor.temperature.warehouse-3 ส่วน binding key ใช้รูปแบบ dotted เดียวกันบวก wildcard สองตัว:
*(star) match หนึ่ง word พอดี#(hash) match ศูนย์ word ขึ้นไป
อ่าน wildcard
หัวข้อที่มีชื่อว่า “อ่าน wildcard”flowchart LR p["publish 'order.eu.created'"] --> x["topic exchange 'events'"] x -->|"order.*.created"| q1["queue: new-orders"] x -->|"order.#"| q2["queue: all-order-events"] x -. "ไม่ match: payment.#" .-> q3["queue: payments"]
ลองไล่ key order.eu.created:
order.*.created→ match (*กลืนeu, word แรกและท้ายเป็น literal)order.#→ match (#กลืนeu.created, กี่ word ตามหลังก็ได้)payment.#→ ไม่ match (word แรกต้องเป็นpayment)order.*→ ไม่ match (*คือ word เดียวพอดี แต่หลังorderมีสอง word)
อันสุดท้ายคือกับดักคลาสสิก: * คือ หนึ่ง word พอดี ไม่ใช่ “หนึ่งขึ้นไป” ถ้าความยาวส่วนท้ายไม่แน่นอนให้ใช้ #
const ex = 'events';await channel.assertExchange(ex, 'topic', { durable: true });
// "all created orders, in any region"const q = await channel.assertQueue('new-orders', { durable: true });await channel.bindQueue(q.queue, ex, 'order.*.created');
// producer: the routing key describes the eventchannel.publish(ex, 'order.eu.created', Buffer.from('...'));ex = "events"channel.exchange_declare(exchange=ex, exchange_type="topic", durable=True)
# "all created orders, in any region"channel.queue_declare(queue="new-orders", durable=True)channel.queue_bind(queue="new-orders", exchange=ex, routing_key="order.*.created")
# producer: the routing key describes the eventchannel.basic_publish(exchange=ex, routing_key="order.eu.created", body="...")ex := "events"ch.ExchangeDeclare(ex, "topic", true, false, false, false, nil)
// "all created orders, in any region"ch.QueueDeclare("new-orders", true, false, false, false, nil)ch.QueueBind("new-orders", "order.*.created", ex, false, nil)
// producer: the routing key describes the eventch.PublishWithContext(ctx, ex, "order.eu.created", false, false, amqp.Publishing{Body: []byte("...")})topic ครอบคลุมทั้ง direct และ fanout
หัวข้อที่มีชื่อว่า “topic ครอบคลุมทั้ง direct และ fanout”topic exchange เลียนแบบอีกสอง type ได้:
- binding key ที่ ไม่มี wildcard (
order.created) ทำงานเหมือน binding แบบ direct เป๊ะ - binding key ที่เป็นแค่
#match ทุกอย่าง ทำงานเหมือน fanout
ดังนั้น หลายทีมจึงตั้ง topic exchange เป็น default สำหรับ event bus — ไม่มีต้นทุนเพิ่มและเหลือที่ให้เพิ่ม subscriber ที่ละเอียดขึ้นทีหลัง trade-off คือวินัย: routing-key scheme ที่ดีและสม่ำเสมอ (domain.detail.action) คือสิ่งที่ทำให้ topic routing อ่านง่ายแทนที่จะยุ่งเหยิง