Smart Routing¶
BunnyFarm's smart routing system automatically creates predictable routing keys based on your message classes and actions. This eliminates the need for manual routing configuration while maintaining full transparency and debuggability.
How Smart Routing Works¶
Routing Key Pattern¶
BunnyFarm uses a simple, predictable pattern for routing keys:
Examples:
- OrderMessage.process
- EmailMessage.send
- ReportMessage.generate
- UserRegistrationMessage.create_account
Automatic Generation¶
When you publish a message, the routing key is automatically generated:
class OrderMessage < BunnyFarm::Message
actions :validate, :process, :ship
end
# Publishing automatically creates routing keys
message = OrderMessage.new
message.publish('validate') # Routing key: OrderMessage.validate
message.publish('process') # Routing key: OrderMessage.process
message.publish('ship') # Routing key: OrderMessage.ship
Routing Flow¶
1. Publisher Side¶
# 1. Create message
order_msg = OrderMessage.new
order_msg[:order_id] = 12345
# 2. Publish with action
order_msg.publish('process')
# 3. BunnyFarm generates routing key: "OrderMessage.process"
# 4. Message sent to RabbitMQ with this routing key
2. RabbitMQ Routing¶
3. Consumer Side¶
# 1. Consumer receives message with routing key "OrderMessage.process"
# 2. BunnyFarm parses routing key → class: OrderMessage, action: process
# 3. Message deserialized to OrderMessage instance
# 4. The 'process' method is called automatically
class OrderMessage < BunnyFarm::Message
def process
# This method is called automatically
puts "Processing order #{@items[:order_id]}"
success!
end
end
Exchange and Queue Configuration¶
Topic Exchange¶
BunnyFarm typically uses RabbitMQ's topic exchange for flexible routing:
BunnyFarm.config do
exchange_name 'bunny_farm_exchange'
exchange_type :topic # Supports pattern matching
end
Queue Bindings¶
Queues can bind to specific routing patterns:
# Bind to all OrderMessage actions
queue.bind(exchange, routing_key: 'OrderMessage.*')
# Bind to specific actions only
queue.bind(exchange, routing_key: 'OrderMessage.process')
# Bind to all messages
queue.bind(exchange, routing_key: '#')
# Bind to all validation actions across message types
queue.bind(exchange, routing_key: '*.validate')
Routing Patterns¶
1. Single Message Type, Single Action¶
Use case: Dedicated worker for specific operations
# Worker specializes in order processing
BunnyFarm.config do
queue_name 'order_processing'
routing_key 'OrderMessage.process'
end
# Only processes OrderMessage.process
BunnyFarm.manage
2. Single Message Type, Multiple Actions¶
Use case: Worker handles all operations for one domain
# Worker handles all order operations
BunnyFarm.config do
queue_name 'order_worker'
routing_key 'OrderMessage.*' # All OrderMessage actions
end
class OrderMessage < BunnyFarm::Message
actions :validate, :process, :ship, :cancel
def validate; end # Handled by order_worker
def process; end # Handled by order_worker
def ship; end # Handled by order_worker
def cancel; end # Handled by order_worker
end
3. Multiple Message Types, Specific Actions¶
Use case: Worker specializes in one type of operation across domains
# Worker handles validation for all message types
BunnyFarm.config do
queue_name 'validation_worker'
routing_key '*.validate' # All validation actions
end
# These would all be handled by validation_worker:
# OrderMessage.validate
# CustomerMessage.validate
# ProductMessage.validate
4. Multiple Message Types, All Actions¶
Use case: General-purpose worker
# Worker handles everything
BunnyFarm.config do
queue_name 'general_worker'
routing_key '#' # All messages
end
Advanced Routing Scenarios¶
Priority Queues¶
Route urgent messages to high-priority queues:
class UrgentOrderMessage < BunnyFarm::Message
actions :process_immediately
end
# High-priority worker
BunnyFarm.config do
queue_name 'urgent_orders'
routing_key 'UrgentOrderMessage.*'
queue_options do
arguments 'x-max-priority' => 10
end
end
# Regular priority worker
BunnyFarm.config do
queue_name 'regular_orders'
routing_key 'OrderMessage.*'
end
Geographic Routing¶
Route messages based on regions:
class USOrderMessage < BunnyFarm::Message
actions :process, :ship
end
class EUOrderMessage < BunnyFarm::Message
actions :process, :ship
end
# US worker
BunnyFarm.config do
queue_name 'us_orders'
routing_key 'USOrderMessage.*'
end
# EU worker
BunnyFarm.config do
queue_name 'eu_orders'
routing_key 'EUOrderMessage.*'
end
Load Balancing¶
Multiple workers can consume from the same queue:
# Worker 1
BunnyFarm.config do
app_id 'worker_1'
queue_name 'order_processing'
routing_key 'OrderMessage.process'
end
# Worker 2
BunnyFarm.config do
app_id 'worker_2'
queue_name 'order_processing' # Same queue
routing_key 'OrderMessage.process'
end
# RabbitMQ automatically load balances between workers
Routing Key Introspection¶
Debugging Routing¶
Check what routing key will be generated:
class OrderMessage < BunnyFarm::Message
actions :process
end
message = OrderMessage.new
routing_key = "#{message.class.name}.process"
puts routing_key # => "OrderMessage.process"
Runtime Routing Information¶
Access routing information during processing:
class OrderMessage < BunnyFarm::Message
def process
puts "Processing with routing key: #{self.class.name}.process"
puts "Message class: #{self.class.name}"
puts "Action: process"
success!
end
end
Error Routing¶
Dead Letter Queues¶
Failed messages can be routed to error queues:
BunnyFarm.config do
queue_name 'order_processing'
routing_key 'OrderMessage.*'
queue_options do
arguments({
'x-dead-letter-exchange' => 'failed_messages',
'x-dead-letter-routing-key' => 'failed.OrderMessage'
})
end
end
Retry Queues¶
Implement retry logic with delayed routing:
class OrderMessage < BunnyFarm::Message
def process
begin
perform_processing
success!
rescue RetryableError => e
if retry_count < 3
# Publish to retry queue with delay
retry_message = self.class.new(@items)
retry_message[:retry_count] = retry_count + 1
retry_message.publish_delayed('process', delay: 30.seconds)
success! # Don't NACK original message
else
failure("Max retries exceeded: #{e.message}")
end
end
successful?
end
private
def retry_count
@items[:retry_count] || 0
end
end
Monitoring and Debugging¶
RabbitMQ Management UI¶
Use RabbitMQ's management interface to monitor routing:
- Exchanges tab - See message routing statistics
- Queues tab - Monitor queue depths and consumption rates
- Connections tab - View active publishers and consumers
Routing Metrics¶
Track routing patterns in your application:
class OrderMessage < BunnyFarm::Message
def process
# Track routing metrics
Metrics.increment("message.routed.#{self.class.name}.process")
# Your processing logic
perform_order_processing
Metrics.increment("message.processed.#{self.class.name}.process")
success!
end
end
Logging Routing Events¶
Log routing information for debugging:
class OrderMessage < BunnyFarm::Message
def process
logger.info "Processing message",
routing_key: "#{self.class.name}.process",
message_id: @items[:order_id]
# Processing logic
success!
end
end
Best Practices¶
1. Predictable Naming¶
Use clear, consistent class and action names:
# Good: Clear domain and action
class CustomerRegistrationMessage < BunnyFarm::Message
actions :validate_email, :create_account, :send_welcome
end
# Avoid: Vague or abbreviated names
class CRM < BunnyFarm::Message
actions :val, :cr8, :snd
end
2. Logical Grouping¶
Group related actions in the same message class:
# Good: Related order operations
class OrderMessage < BunnyFarm::Message
actions :validate, :process_payment, :fulfill, :ship, :complete
end
# Avoid: Mixing unrelated operations
class MixedMessage < BunnyFarm::Message
actions :process_order, :send_email, :backup_database, :clean_temp_files
end
3. Queue Design¶
Design queues around processing capabilities:
# Good: Separate concerns
BunnyFarm.config do
case worker_type
when 'payment_processor'
routing_key '*.process_payment'
when 'email_sender'
routing_key '*.send_email'
when 'order_validator'
routing_key 'OrderMessage.validate'
end
end
4. Error Handling¶
Plan for routing errors:
def process
validate_message_structure
return unless successful?
perform_business_logic
return unless successful?
log_success
end
private
def validate_message_structure
required_fields = [:order_id, :customer_id, :amount]
missing = required_fields.select { |field| @items[field].nil? }
failure("Missing required fields: #{missing.join(', ')}") if missing.any?
end
Next Steps¶
Understanding smart routing enables you to:
- Configure routing for your specific needs
- Design message structures that route effectively
- Scale your architecture with proper queue design
- Handle errors with appropriate routing strategies