This guide will help you migrate your code from amqp-client v1.x to v2.0. Version 2.0 introduces several breaking changes that improve API consistency, clarity, and functionality.
Impact: ALL public API methods
All public methods now use keyword arguments instead of positional arguments for improved clarity and to prevent argument order mistakes.
Publishing:
# v1.x
amqp.publish("my message", "amq.topic", "routing.key", headers: { foo: "bar" })
queue.publish("body", content_type: "application/json")
exchange.publish("body", "routing.key")
# v2.0
amqp.publish("my message", exchange: "amq.topic", routing_key: "routing.key", headers: { foo: "bar" })
queue.publish("body", content_type: "application/json")
exchange.publish("body", routing_key: "routing.key")Exchange Declaration:
# v1.x
amqp.exchange("my.exchange", "x-consistent-hash", durable: true)
# v2.0
amqp.exchange("my.exchange", type: "x-consistent-hash", durable: true)Queue Binding:
# v1.x
queue.bind("amq.topic", "routing.key")
# v2.0
queue.bind("amq.topic", binding_key: "routing.key")
# Or better, use the exchange object:
exchange = amqp.topic_exchange("amq.topic")
queue.bind(exchange, binding_key: "routing.key")Publishing:
# v1.x
channel.basic_publish("body", "exchange", "routing.key", persistent: true)
channel.basic_publish_confirm("body", "exchange", "routing.key", persistent: true)
# v2.0
channel.basic_publish("body", exchange: "exchange", routing_key: "routing.key", persistent: true)
channel.basic_publish_confirm("body", exchange: "exchange", routing_key: "routing.key", persistent: true)Exchange Declaration:
# v1.x
channel.exchange_declare("my.exchange", "topic", durable: true)
# v2.0
channel.exchange_declare("my.exchange", type: "topic", durable: true)Impact: Code using convenience methods for exchange types
Exchange convenience methods have been renamed to include _exchange suffix for improved clarity:
# v1.x
amqp.direct("my.exchange")
amqp.fanout("my.exchange")
amqp.topic("my.exchange")
amqp.headers("my.exchange")
# v2.0
amqp.direct_exchange("my.exchange")
amqp.fanout_exchange("my.exchange")
amqp.topic_exchange("my.exchange")
amqp.headers_exchange("my.exchange")Impact: Code relying on the direct exchange method to get the default exchange
The default name for the direct exchange has changed from an empty string (the default exchange) to "amq.direct" for API consistency:
# v1.x
amqp.direct() # Returns the default exchange
amqp.direct("") # Returns the default exchange
# v2.0
amqp.direct_exchange() # Returns exchange with name "amq.direct"
amqp.direct_exchange("") # Returns the default exchangeMigration:
- If you were relying on
direct()to return the default exchange (empty string), usedirect_exchange("")explicitly - You should probably use
default_exchangerather thandirect_exchange("").*
Impact: Code using Client#subscribe or Queue#subscribe
The subscribe methods now return a Consumer object which can be used to cancel the subscription:
# v1.x
queue.subscribe(prefetch: 10) do |msg|
puts msg.body
end
# No way to cancel the subscription
# v2.0
consumer = queue.subscribe(prefetch: 10) do |msg|
puts msg.body
end
# Can now cancel:
consumer.cancelMigration:
- If you don't need to cancel subscriptions, you can ignore the return value
- To cancel a subscription, store the returned
Consumerobject and callcancelon it
Impact: Low-level API code using basic_subscribe
The Channel#basic_subscribe method now returns Connection::Channel::ConsumeOk for better consumer response handling:
# v1.x
channel.basic_consume(queue_name, no_ack: false) do |msg|
# ...
end
# Returned some internal value
# v2.0
consume_ok = channel.basic_consume(queue_name, no_ack: false) do |msg|
# ...
end
# consume_ok has: consumer_tagMigration:
- If you weren't using the return value, no changes needed
- If you were using it, update to use the
ConsumeOkstructure
Impact: Code inspecting QueueOk structure internals
Connection::Channel::QueueOk has been converted from a Struct to a Data class (immutable):
# v1.x
queue_ok = channel.queue_declare("my.queue")
queue_ok.queue_name = "something else" # Mutable
# v2.0
queue_ok = channel.queue_declare(name: "my.queue")
queue_ok.queue_name = "something else" # Error: Data objects are immutableMigration:
- If you were only reading fields, no changes needed
- If you were modifying the structure, you'll need to create new instances instead
While not breaking changes, these new features may allow you to simplify your code:
A new unified configuration API has been introduced:
AMQP::Client.configure do |config|
config.enable_builtin_codecs
config.default_content_type = "application/json"
config.default_content_encoding = "gzip"
config.strict_coding = true
# Can also register custom parsers/coders
config.register_parser(content_type: "application/msgpack", parser: MsgPackParser)
endThe high-level API now supports automatic message encoding and serialization:
# Enable built-in codecs via configure block
AMQP::Client.configure do |config|
config.enable_builtin_codecs
end
# Automatically serializes to JSON
queue.publish({ foo: "bar" }, content_type: "application/json")
# Automatically deserializes based on content_type
queue.subscribe do |msg|
data = msg.parse # Returns the parsed Ruby hash
endSupported formats:
application/json- JSON encoding/decodinggzip- Gzip compressiondeflate- Deflate compression
You can set default content_type and content_encoding in the configure block:
# Class-level defaults via configure block
AMQP::Client.configure do |config|
config.default_content_type = "application/json"
config.default_content_encoding = "gzip"
end
# Instance-level override
amqp = AMQP::Client.new("amqp://localhost")
amqp.default_content_type = "text/plain"
# These will be applied automatically unless explicitly overridden
queue.publish({ foo: "bar" }) # Automatically uses application/jsonQueue#subscribe now handles acknowledgments and rejections automatically:
# Automatic handling
queue.subscribe(prefetch: 20) do |msg|
process(msg)
# Message is automatically ack'd if block returns successfully
# Message is automatically rejected (with requeue) if block raises an exception
end
# Manual handling still works if you prefer
queue.subscribe do |msg|
msg.ack
rescue => e
msg.reject(requeue: false)
endA new RPC API has been added for request-response patterns:
# Server
amqp.rpc_server("rpc_queue") do |request|
{ result: request[:value] * 2 }
end
# Client
rpc = amqp.rpc_client
response = rpc.call({ value: 21 }, routing_key: "rpc_queue")
# => { result: 42 }You can now poll messages from queues as an alternative to subscribing:
# Get a single message (returns nil if queue is empty)
msg = queue.get
# Or using the client
msg = amqp.get(queue: "my.queue")
if msg
process(msg)
msg.ack
endNew parameters:
passive- Check if queue exists without creating itexclusive- Queue will be deleted when connection closes
- Update all
publishcalls to use keyword arguments - Update all
exchange_declarecalls to usetype:keyword - Update all
queue_declarecalls to use keyword arguments - Rename
direct(),fanout(),topic(),headers()to*_exchange()variants - Review uses of
direct()with no arguments - may need to explicitly pass"" - If cancelling subscriptions, store the returned
Consumerobject - Update any code that was mutating
QueueOkstructures - Replace
require "amqp-client/enable_builtin_codecs"with configure block - Consider using automatic message encoding for JSON/gzip/deflate
- Consider using automatic ack/reject in
Queue#subscribe - Review and test all queue bindings to use the new syntax
If you encounter issues during migration:
- Check the API documentation
- Review the examples in the README
- Open an issue on GitHub
- Contact CloudAMQP support