AceMQ for Ruby
A Ruby client for AceMQ messaging over AMQP, speaking the same wire contract as the Java, Go, .NET and Python libraries: the same reserved headers, the same defaults, the same retry arithmetic. A Ruby consumer reads what a Java producer writes.
That claim is pinned rather than asserted. The fixtures under
spec/fixtures/ were generated by the Java implementation
and are shared with Go, .NET and Python:
envelope-fixtures.json pins the headers, and
contract-fixtures.json pins the retry schedule, the queue
names and the topology. Specs read both and hold this library to them. A
port that hand-copies a contract out of documentation acquires a
difference nobody notices until two languages disagree in production —
see how the five
libraries are kept in step, including the places they do not yet
agree.
A message, end to end
require "acemq/amqp"
include AceMQ::AMQP
mq = Connection.open("amqp://guest:guest@localhost:5672",
origin: "checkout@pod-7",
retry_policy: RetryPolicy.exponential(5, 1, 60))
Topology.new
.exchange("orders-events", :topic)
.queue("orders.new", dead_letter: true)
.binding("orders.new", "orders-events", "order.#")
.apply(mq)
mq.publish({ "order_id" => "A-1" }, to: "order.placed",
exchange: "orders-events", type: "order.placed.v2")
mq.consume("orders.new") do |message|
message.payload # => { "order_id" => "A-1" }
message.envelope.attempt # => which delivery this is
warehouse.reserve(message.payload) ? Ack.accept : Ack.retry("the warehouse said no")
endGetting started takes this apart line by line, and the tutorials build a working service out of it in four steps.
What it does for you
An envelope on every message. Identity, type, version, correlation, causation, attempt count, origin and the time it was first published — the same fields Java, Go, .NET and Python write, so a message crossing between them keeps its history. Application headers are kept apart from those, and a reserved name in your own headers raises rather than being dropped. See the envelope.
Retries that stop, and wait in the right place. A
handler returns Ack.retry, and a policy decides whether
there is another attempt and how long to wait. A short wait happens in
the consumer; a long one happens in the broker, in a rung queue whose
time-to-live is the delay — because a consumer that sleeps through a
five-minute backoff loses the whole wait when it restarts. See retries, dead letters and shutdown.
Nothing to install to read a message. The gem has no
runtime dependencies. bunny is required lazily, at the
moment a connection is opened, and named in the error if it is not
there, so a process that only builds envelopes or checks a retry
schedule needs no broker client at all. See testing
without a broker.
Security you have to opt out of. An
amqps:// URL is encrypted and verified, which is
not what the underlying driver does on its own — see security, which explains the bunny behaviour this
library exists to correct. Credentials are a separate object so the
password never goes in the URL, and they render redacted through
inspect, to_s and %p alike.
A seam for the things no library can guess. A tenant, a trace context, a log scope, a size limit. Interceptors wrap publishing and handling without anybody editing the engine.
Queues declared the way the other libraries declare
them. A durable, named queue is a quorum queue, because
x-queue-type is part of a queue's identity to the broker
and the second service to declare orders differently cannot
consume at all. See queue
types.
Numbers, traces and an honest readiness answer. Metric names shared with the other four libraries, an in-memory registry with a Prometheus renderer, an OpenTelemetry adapter that joins a handler's span to the publish that caused it across processes and minutes, and a health report that calls a stopped consumer under a live connection degraded rather than down, and a broker applying back pressure up, with the reason rather than down. See metrics, tracing and health.
A body the broker cannot read.
EncryptedCodec wraps whichever codec you already use and
encrypts what it produced with AES-GCM, with a keyring so a key can be
rotated while messages written with the old one are still queued. See encrypting the body.
Development certificates that cannot reach production. One command writes the authority and certificates a local TLS broker needs, and every one of them is stamped so that this library refuses it however trust is configured — unverified mode included. See development certificates.
The things everybody writes anyway, written once. Idempotency, the outbox, request and reply, replay, ordering, consumer groups, routing slips, pipelines, schemas and streams. Required separately, because none of it is protocol. See patterns.
What is identical, and what is not
Identical, because a message crosses languages: the
reserved header names and their types, the defaults applied when they
are absent, the retry schedule arithmetic, the {queue}.dlq
/ {queue}.parked / {queue}.retry.{delay}
naming, and the rules for giving up.
Not identical, deliberately: the API shape. Go gets
ctx, .NET gets IAsyncEnumerable, Python gets
dataclasses, and Ruby gets keyword arguments, frozen value objects,
blocks and ? predicates. Forcing a Java shape onto Ruby
produces a library nobody enjoys using. The contract is portable; the
ergonomics are native.
Concretely, that means: Envelope and
Credentials are frozen after construction and changed with
with; a handler is a block that returns an
Ack; every optional argument is a keyword argument; and a
pattern that wraps a handler hands back a block you pass straight to
consume.
The layout
Everything lives under AceMQ::AMQP, in one gem, with the
patterns behind a second require:
acemq/amqp |
envelopes, headers, codecs, retry policy, topology, the connection and the transport |
acemq/amqp/patterns |
idempotency, outbox, request-reply, replay, ordering, consumer groups, routing slips, sagas, scheduling, pipelines, schemas, streams |
require "acemq/amqp"
require "acemq/amqp/patterns" # only if you use theminclude AceMQ::AMQP at the top of a file is how the
examples here are written, because Connection,
Topology, Ack and RetryPolicy
read better than the fully qualified names at every call site. Nothing
depends on it — qualify them if you would rather.
Installing
# Gemfile
source "https://acemq.org/gems" do
gem "acemq-amqp", "~> 0.7", ">= 0.7.14"
end
gem "bunny", ">= 2.23", "< 4" # for the transportRuby 3.1 or newer. RabbitMQ for the broker.
The gem itself declares no runtime dependencies, and three of the nine codecs are the only other things that ever ask for one. Each requires its gem lazily and names it when it is missing:
gem "rexml", "~> 3.3" # XMLCodec. Ships with Ruby, but is a bundled
# gem since 3.4, so Bundler needs it named.
gem "google-protobuf", "~> 4.29" # ProtobufCodec
gem "avro", "~> 1.12" # AvroCodec
gem "opentelemetry-api", "~> 1.8" # Telemetry::OpenTelemetryThe gem is not on rubygems.org. Releases go to AceMQ's own feed at https://acemq.org/gems, which is what
the source block above names; the current one is 0.7.14.
The releasing
section of the README says why.
Where to go next
Start at one of two doors. Getting started explains the first message line by line; the tutorials are four sittings that end with a service that publishes, retries, deduplicates and says so.
Then the guide:
| Getting started | the first message, explained |
| Tutorials | four, in order, each ending with something that runs |
| Publishing | one message, a batch, and what a confirm does and does not promise |
| Consuming | handlers, acks, prefetch and concurrency |
| Exchanges, queues and bindings | where a message can actually go, and why a queue is quorum |
| The envelope | what travels with a message |
| Codecs | JSON, YAML, TOML, XML, Protobuf, Avro, text or raw bytes, and encrypted with AES-GCM |
| Interceptors | the seam around publish and handle |
| Security | TLS, trust, credentials and development certificates that cannot reach production |
| Retries, dead letters and shutdown | the retry ladder, the dead-letter queue and the parking lot |
| Patterns | idempotency, outbox, replay, ordering and nine more |
| Request and reply | asking a question and blocking for the answer, and what that costs |
| Streams | a queue that keeps what it delivered, and the machinery that does not apply to one |
| Metrics, tracing and health | including the OpenTelemetry adapter |
| Testing without a broker | the transport seam, and where it stops being enough |
| API reference | every public class and method, from YARD |
Status
Pre-1.0 and in development. The contract layer and the AMQP transport — connect, publish, consume, retry, dead-letter — are implemented and tested, against the shared fixtures and against a real broker, and so are the patterns above them. TLS and credentials are in, including a private certificate authority and mutual TLS. The API may change before 1.0. The current release is 0.7.14, from the gem feed at https://acemq.org/gems.