AceMQ for Go
A Go client for AceMQ messaging over AMQP, speaking the same wire contract as the Java and .NET libraries: the same reserved headers, the same defaults, the same retry semantics. A Go consumer reads what a Java producer writes.
That claim is pinned rather than asserted. The fixtures under
internal/testdata/ were generated by the Java
implementation and are carried byte for byte by all five libraries. One
holds the headers: a test reads each case and writes it back, and no
header is gained, lost or renamed. The other holds the retry schedule,
the queue names and the topology, down to the thirty rows that decide
whether a wait is spent here or on the broker. A port that hand-copies a
wire contract from documentation acquires a difference nobody notices
until two languages disagree in production — which is exactly how Java
came to multiply its backoff by five while everything else doubled, for
ten releases, with every suite green. See how we know the
five libraries agree.
go get github.com/AceMQ-Company/acemq-go-amqpA message, end to end
import (
acemq "github.com/AceMQ-Company/acemq-go-amqp/amqp"
_ "github.com/AceMQ-Company/acemq-go-amqp/rabbitmq"
)
type OrderPlaced struct {
OrderID string `json:"orderId"`
TotalCents int64 `json:"totalCents"`
}
mq, err := acemq.Connect(ctx, "amqp://guest:guest@localhost:5672/",
acemq.WithRetry(acemq.ExponentialRetry(5, time.Second, time.Minute)))
if err != nil {
return err
}
defer mq.Close()
if err := mq.DeclareQueue(ctx, "orders"); err != nil {
return err
}
pub := acemq.NewPublisher[OrderPlaced](mq, "", "orders")
if err := pub.Send(ctx, OrderPlaced{OrderID: "o-1", TotalCents: 4250}); err != nil {
return err
}
sub, err := acemq.Consume(ctx, mq, "orders",
func(ctx context.Context, m acemq.Message[OrderPlaced]) acemq.Ack {
if err := place(ctx, m.Payload); err != nil {
return acemq.Retry(err)
}
return acemq.Accept()
})
defer sub.Close()Getting started takes this apart line by line, and the tutorials build a working service in four steps.
What it does for you
An envelope on every message. Identity, type, correlation, causation, attempt count, origin and the time it was first published — the same fields the Java and .NET libraries write, so a message crossing between them keeps its history. See the envelope.
Retries that stop. A handler returns Retry, and a policy decides whether
there is another attempt and how long to wait. The attempt count comes
from the broker's redelivery flag rather than the header on the wire,
because a broker requeues the bytes it was given and that header never
moves. See retries and redelivery.
A broker you do not need. memory:// is
built in, so tests exercise real publishing, consuming, acknowledgement
and retry without Docker. It is deliberately no kinder than RabbitMQ.
See testing.
Security you have to opt out of. TLS with a named authority, credentials read fresh on every connection, and development certificates that are refused unless you say otherwise — on every path, including the insecure one. See security.
A core with no dependencies. Nothing outside the
standard library, including the UUIDs. The AMQP client lives behind a
transport registry in a subpackage, so a program that only uses
memory:// never compiles it in, and every format beyond
JSON is a module of its own.
Connections that come back. A dropped connection is redialled, the topology redeclared and every consumer reattached — because the alternative is a service that silently stops consuming and says nothing. See retries and redelivery.
The layout
The module is a package per concern, with nothing at its root:
.../acemq-go-amqp/amqp |
envelopes, codecs, publishing, consuming, retry, the in-memory transport |
.../acemq-go-amqp/rabbitmq |
the RabbitMQ transport, on
github.com/rabbitmq/amqp091-go |
.../acemq-go-amqp/security |
TLS modes, trusted authorities, credentials |
.../acemq-go-amqp/patterns |
request-reply, idempotency, outbox, ordering, pipelines, replay, routing slips, claim check, sagas, delayed delivery, streams, consumer groups, schema registry, SQL stores |
.../acemq-go-amqp/actuator |
metrics, health and info over HTTP |
.../acemq-go-amqp/crypto |
encrypted message bodies |
.../acemq-go-amqp/codec/... |
XML, YAML, TOML, Protocol Buffers, Avro — a module each |
.../acemq-go-amqp/devcerts |
development certificates, behind the acemq-certs
command |
The package in amqp/ is named acemq, so the
import carries the name:
import acemq "github.com/AceMQ-Company/acemq-go-amqp/amqp"The directory says what the package is; the name says whose it is. It
also keeps the module root free for the packages that will sit beside
these two, and acemq.Connect reads better at every call
site than amqp.Connect would.
The blank import is what registers the amqp and
amqps schemes:
import _ "github.com/AceMQ-Company/acemq-go-amqp/rabbitmq"The same arrangement database/sql uses for its drivers,
and for the same reason: the program says which broker it talks to, and
nothing else is linked in.
One difference from Java and .NET
NewPublisher and Consume are functions
taking the connection rather than methods on it, because a method cannot
have its own type parameter before Go 1.27:
func (c *Conn) Publisher[T any](...) // generic method: needs go1.27
func NewPublisher[T any](c *Conn, ...) // generic function: works everywhereThis module declares go 1.23. For a library, working
with the toolchains people actually have matters more than mirroring the
exact shape of another language's API. Publisher[T] is a
generic type with ordinary methods, which has no such
restriction, so once you hold one it reads much as it does
elsewhere.
Where to go next
- Tutorials — four of them, in order, each ending with something that runs
- Getting started — the first message, explained
- Publishing and consuming
- Exchanges, queues and bindings
- The envelope — what travels with every message
- Interceptors — one place for what belongs on every message
- Security — TLS, trust and credentials
- Retries, redelivery and shutdown
- The lifecycle of a service — signals, graceful drain, bounded shutdown, and what a probe should say
- Patterns — idempotency, outbox, ordering, pipelines, replay, routing slips, claim check, sagas, delayed delivery, consumer groups, schema registry
- Request and reply — the synchronous shape, and its cost
- Streams — a log that keeps what it is given
- Codecs — JSON, XML, YAML, TOML, Protocol Buffers, Avro
- Metrics, tracing and health
- Testing without a broker
- API reference on pkg.go.dev
Status
Pre-1.0 and in development. The API may change before 1.0.