AMQP bindings aren’t just passive links; they’re active instructions that determine where messages can go, while routing is the dynamic decision-making process that dictates where they actually go.
Let’s see this in action with RabbitMQ. Imagine a scenario where we have a fanout exchange, logs, and we want to send messages to two different queues, app_logs and error_logs.
First, we declare our exchange and queues:
rabbitmqadmin declare exchange name=logs type=fanout
rabbitmqadmin declare queue name=app_logs
rabbitmqadmin declare queue name=error_logs
Now, the critical part: the bindings. A binding connects an exchange to a queue. For a fanout exchange, every message sent to it is delivered to all queues bound to it, regardless of any routing keys.
rabbitmqadmin declare binding source=logs destination=app_logs
rabbitmqadmin declare binding source=logs destination=error_logs
With these bindings in place, if we publish a message to the logs exchange:
rabbitmqadmin publish exchange=logs routing_key="" payload="This is a general log message."
This single message will appear in both the app_logs queue and the error_logs queue. The routing_key is ignored by fanout exchanges.
Now, let’s contrast this with a direct exchange, events. We want user_events to receive messages with a routing key user.login and payment_events to receive messages with a routing key payment.processed.
rabbitmqadmin declare exchange name=events type=direct
rabbitmqadmin declare queue name=user_events
rabbitmqadmin declare queue name=payment_events
The bindings are where we specify the routing keys:
rabbitmqadmin declare binding source=events destination=user_events routing_key=user.login
rabbitmqadmin declare binding source=events destination=payment_events routing_key=payment.processed
If we publish a message to the events exchange with the routing key user.login:
rabbitmqadmin publish exchange=events routing_key=user.login payload='{"user_id": "abc", "action": "login"}'
This message will only go to the user_events queue because its binding to the events exchange matches the user.login routing key. A message with payment.processed would go to payment_events, but a message with user.signup would be dropped unless another queue was bound with that specific key.
The real power comes with topic exchanges. Let’s set up a notifications exchange. We want to send email notifications for user.created and user.deleted events, and sms notifications for payment.failed events.
rabbitmqadmin declare exchange name=notifications type=topic
rabbitmqadmin declare queue name=email_queue
rabbitmqadmin declare queue name=sms_queue
The bindings use pattern matching:
rabbitmqadmin declare binding source=notifications destination=email_queue routing_key=user.*
rabbitmqadmin declare binding source=notifications destination=sms_queue routing_key=payment.failed
If we publish a message with user.created:
rabbitmqadmin publish exchange=notifications routing_key=user.created payload="New user registered."
This message will go to email_queue because user.* matches user.created. The * acts as a wildcard for a single word.
If we publish a message with payment.failed:
rabbitmqadmin publish exchange=notifications routing_key=payment.failed payload="Payment transaction failed."
This message will go to sms_queue because payment.failed is an exact match to the binding’s routing key.
What if we wanted to send all payment-related notifications (failed, successful, refunded) to the sms_queue? We could use the # wildcard, which matches zero or more words:
rabbitmqadmin declare binding source=notifications destination=sms_queue routing_key=payment.#
Now, a message with payment.successful would also be routed to sms_queue.
The mental model is this: the exchange is the post office. Queues are mailboxes. Bindings are the rules written on the mailboxes saying "I accept mail addressed with this specific code (routing key)." The publisher sends a letter with an address (routing key) to the post office (exchange). The post office then looks at all the mailboxes (queues) it has, checks their rules (bindings), and delivers the letter to any mailbox whose rule matches the address. For fanout, the rule is "accept everything." For direct, it’s "accept only this exact address." For topic, it’s "accept addresses matching this pattern."
A common point of confusion is how routing keys are interpreted. For topic exchanges, the routing key is a dot-separated string of words. The binding’s routing key can use * to match a single word (e.g., news.uk.* matches news.uk.headlines but not news.uk.today.latest) or # to match zero or more words (e.g., news.# matches news, news.uk, news.uk.headlines, news.us.today.latest). The matching is done by the exchange itself based on the binding rules.
The next concept to explore is message acknowledgments, which guarantee message delivery even if consumers crash.