rabbitmq

package
v1.8.2 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 19, 2026 License: MIT Imports: 12 Imported by: 0

README

rabbitmq

Wrapper around github.com/furdarius/rabbitroutine for AMQP publishing and consumption with the lifecycle contract: typed Config, functional Options, Shutdown(ctx), explicit Resource (Publisher) vs Runner (ConsumerHost) roles.

When to use

Any service that publishes or consumes RabbitMQ messages.

Quickstart

package main

import (
    "context"
    "log"

    "github.com/sergeyslonimsky/core/app"
    "github.com/sergeyslonimsky/core/rabbitmq"
)

func main() {
    ctx := context.Background()
    cfg := rabbitmq.Config{Host: "rabbitmq", Port: "5672", User: "app", Password: "secret"}

    publisher, err := rabbitmq.NewPublisher(ctx, cfg)
    if err != nil { log.Fatal(err) }

    host := rabbitmq.NewConsumerHost(cfg)
    host.AddConsumer(rabbitmq.NewConsumer[OrderEvent](
        "orders", &orderProcessor{},
        rabbitmq.WithExchange(rabbitmq.ExchangeConfig{
            Name: "orders.exchange", Kind: rabbitmq.ExchangeKindTopic, Durable: true,
        }),
        rabbitmq.WithQueue(rabbitmq.QueueConfig{Durable: true}),
        rabbitmq.WithBindQueue(rabbitmq.BindQueueConfig{
            Exchange: "orders.exchange", RoutingKey: "orders.*",
        }),
    ))

    a := app.New()
    a.Add(publisher)  // Resource — connects in NewPublisher, shuts down here
    a.Add(host)       // Runner   — Run starts consumer loops
    log.Fatal(a.Run())
}

Configuration

type Config struct {
    Host, Port, User, Password string
}

Options

Connector
  • WithReconnectAttempts(uint) — default 3.
  • WithReconnectWait(time.Duration) — default 2s.
  • WithRetriedListener(func(rabbitroutine.Retried))
  • WithDialedListener(func(rabbitroutine.Dialed))
  • WithAMQPNotifiedListener(func(rabbitroutine.AMQPNotified))
Publisher
  • WithPublisherLogger(*slog.Logger) — for lifecycle events.
  • WithPublisherConnector(ConnectorInterface) — share a connector with a host.
  • WithPublisherConnectorOptions(...ConnectorOption) — applied to the auto-built connector.
  • WithPublishMaxAttempts(uint) — default 16.
  • WithPublishLinearDelay(time.Duration) — default 10ms.
ConsumerHost
  • WithHostLogger(*slog.Logger)
  • WithHostConnector(ConnectorInterface) — share with a publisher.
  • WithHostConnectorOptions(...ConnectorOption)
Consumer[I]

Options are NOT generic — the payload type parameter I lives on NewConsumer[I] and Consumer[I] only:

  • WithConsumerConfig(ConsumerConfig) — Consume call settings (AutoAck, Exclusive, etc.).
  • WithExchange(ExchangeConfig) — declare exchange in Declare.
  • WithQueue(QueueConfig) — declare queue.
  • WithBindQueue(BindQueueConfig) — bind queue to exchange.
  • WithConsumeOpts(ConsumeOpts) — QoS prefetch.
  • WithWorkerCount(int) — number of goroutines processing deliveries for this consumer. Default: 1 (ordered, matches default PrefetchCount=1). Raise with care: >1 loses per-queue order.
  • WithConsumerLogger(*slog.Logger) — per-message error logging. Default: slog.Default().

To install a custom body marshaller (the only type-dependent knob), use Consumer[I].SetMarshaller:

c := rabbitmq.NewConsumer[OrderEvent]("orders", processor,
    rabbitmq.WithQueue(queueCfg),
)
c.SetMarshaller(func(body []byte, dst *OrderEvent) error { /* ... */ return nil })
host.AddConsumer(c)

Observability

WithOtel is intentionally not provided yet. The reason: rabbitroutine's reconnect-loop layer makes per-publish span attribution awkward (spans need to survive a reconnect), and there is no upstream kotel-equivalent for amqp091-go. Manual span injection is straightforward inside your Processor.Process method:

func (p *orderProcessor) Process(ctx context.Context, msg rabbitmq.Message[OrderEvent]) error {
    ctx, span := otel.Tracer("orders").Start(ctx, "process_order")
    defer span.End()
    // ...
}

Lifecycle

  • Publisherlifecycle.Resource. Connects in the constructor; reconnect loop runs in a background goroutine rooted in context.Background (NOT the constructor's ctx — that only bounds the initial dial). Shutdown(ctx) cancels the loop and waits with the ctx as a deadline. This is the ONLY correct way to stop the publisher.
  • ConsumerHostlifecycle.Runner. Connects + starts every registered consumer when Run(ctx) is called. Shutdown(ctx) cancels and waits.
  • Consumer[I].Consume uses a bounded worker pool (see WithWorkerCount). On ctx cancellation or broker disconnect, all workers drain their current Ack/Nack before the consume loop returns — no Ack-after-close races.
  • Healthchecker is intentionally not implemented — see "Observability" rationale.

Recommended ordering inside app.App:

a.Add(otelProvider)  // Resource — first registered, last shut down
a.Add(publisher)     // Resource — sustained connection, drains on shutdown
a.Add(consumerHost)  // Runner   — fans out consumers

Extending

inner := connector.Inner()  // *rabbitroutine.Connector for raw access

Testing

Unit tests cover constructor wiring without a live broker. Integration tests run against a real broker via testcontainers and live in internal/integration/ (separate Go module).

See also

Documentation

Overview

Package rabbitmq wraps github.com/furdarius/rabbitroutine with the lifecycle contract used across core: typed Config, functional Options, Shutdown methods, and explicit Resource/Runner roles.

The package exposes:

  • Connector — low-level dial + reconnect manager (used by both Publisher and ConsumerHost).
  • Publisher — Resource. Connects in the constructor; reconnect loop runs as an internal background goroutine and stops in Shutdown.
  • ConsumerHost — Runner. Holds a set of consumers and runs their amqp loops under the same connection.
  • Consumer[I] — generic per-queue consumer logic (declare + consume + marshal). Attached to a ConsumerHost.

Publisher is a Resource (not a Runner): callers register it for Shutdown only, not for Run. ConsumerHost is the Runner that hosts consumer loops.

Index

Constants

This section is empty.

Variables

View Source
var ErrUnsupportedMessage = errors.New("unsupported message type")

ErrUnsupportedMessage is returned by the default body marshaller when the AMQP delivery's ContentType is not "application/json" and no custom marshaller was provided.

Functions

This section is empty.

Types

type BindQueueConfig

type BindQueueConfig struct {
	Exchange   string
	RoutingKey string
	NoWait     bool
	Args       amqp.Table
}

BindQueueConfig governs ch.QueueBind.

type BodyMarshaller

type BodyMarshaller[I any] func(body []byte, payload *I) error

BodyMarshaller decodes a raw AMQP delivery body into a typed payload.

type Config

type Config struct {
	Host     string
	Port     string
	User     string
	Password string
}

Config describes a RabbitMQ connection. Plain fields, no struct tags — consumer apps map their viper keys to fields explicitly inside their own config.NewConfig().

func (Config) DSN

func (c Config) DSN() string

DSN returns the AMQP URL form of the config. Used internally by Connector to dial the broker.

type Connector

type Connector struct {
	// contains filtered or unexported fields
}

Connector wraps a rabbitroutine.Connector with reconnect configuration and listener wiring. Created via NewConnector or implicitly inside NewPublisher / NewConsumerHost.

func NewConnector added in v1.3.0

func NewConnector(cfg Config, opts ...ConnectorOption) *Connector

NewConnector builds a Connector from the given Config and options. Does NOT dial — use Connect (or have NewPublisher / NewConsumerHost call it for you).

func (*Connector) Connect

func (c *Connector) Connect(ctx context.Context) error

Connect dials the broker. Subsequent reconnects are managed internally. Blocks until the first connection is established or ctx expires.

func (*Connector) Inner added in v1.3.0

func (c *Connector) Inner() *rabbitroutine.Connector

Inner returns the underlying *rabbitroutine.Connector. Use for advanced listener wiring or to integrate with code that expects the raw type.

func (*Connector) StartConsumer

func (c *Connector) StartConsumer(ctx context.Context, consumer rabbitroutine.Consumer) error

StartConsumer attaches a rabbitroutine.Consumer and runs its loop until ctx is cancelled. Typically called by ConsumerHost.Run, not directly.

type ConnectorInterface added in v1.3.0

type ConnectorInterface interface {
	// Connect blocks until the broker is reachable and the connection is
	// established. Subsequent reconnects are handled internally by
	// rabbitroutine.
	Connect(ctx context.Context) error

	// Inner returns the underlying *rabbitroutine.Connector for advanced
	// use (custom listeners, channel pools).
	Inner() *rabbitroutine.Connector

	// StartConsumer attaches a rabbitroutine.Consumer to the connector and
	// runs its loop until ctx is cancelled.
	StartConsumer(ctx context.Context, consumer rabbitroutine.Consumer) error
}

ConnectorInterface is the surface used by Publisher and ConsumerHost to drive the underlying rabbitroutine.Connector. Mainly an extension seam: tests can substitute fakes; production code uses *Connector.

The concrete struct is Connector; the interface is ConnectorInterface to keep call sites readable and avoid name collision.

type ConnectorOption added in v1.3.0

type ConnectorOption func(*connectorOptions)

ConnectorOption configures a Connector.

func WithAMQPNotifiedListener

func WithAMQPNotifiedListener(fn func(rabbitroutine.AMQPNotified)) ConnectorOption

WithAMQPNotifiedListener attaches a callback fired when the broker sends an AMQP notification (channel close, blocked, etc).

func WithDialedListener

func WithDialedListener(fn func(rabbitroutine.Dialed)) ConnectorOption

WithDialedListener attaches a callback fired on every successful dial.

func WithReconnectAttempts added in v1.3.0

func WithReconnectAttempts(n uint) ConnectorOption

WithReconnectAttempts overrides how many reconnect attempts the underlying rabbitroutine.Connector makes before giving up. Default: 3.

func WithReconnectWait added in v1.3.0

func WithReconnectWait(d time.Duration) ConnectorOption

WithReconnectWait overrides the delay between reconnect attempts. Default: 2 seconds.

func WithRetriedListener

func WithRetriedListener(fn func(rabbitroutine.Retried)) ConnectorOption

WithRetriedListener attaches a callback fired on every reconnect retry. Useful for surfacing reconnect activity in logs or metrics.

type ConsumeOpts

type ConsumeOpts struct {
	PrefetchCount int
	PrefetchSize  int
	Global        bool
}

ConsumeOpts governs ch.Qos prefetch settings.

type Consumer

type Consumer[I any] struct {
	// contains filtered or unexported fields
}

Consumer is a per-queue consumer with declare-and-consume logic. Attach to a ConsumerHost via host.AddConsumer(consumer).

func NewConsumer

func NewConsumer[I any](
	queue string,
	processor Processor[I],
	opts ...ConsumerOption,
) *Consumer[I]

NewConsumer creates a Consumer for the given queue and processor. By default the delivery body is JSON-unmarshalled into the payload type I; call SetMarshaller to override.

All ConsumerOption values configure declare/consume behavior.

func (*Consumer[I]) Consume

func (c *Consumer[I]) Consume(ctx context.Context, ch *amqp.Channel) error

Consume sets QoS, opens the consume stream, and processes deliveries until ctx is cancelled. Called by rabbitroutine.

Concurrency: a bounded worker pool sized by WithWorkerCount (default 1) pulls from the delivery channel. On ctx cancellation or channel close, Consume stops distributing new work and waits for all in-flight workers to finish their current Ack/Nack before returning. This prevents Ack-after-close panics and gives in-flight messages a chance to settle.

func (*Consumer[I]) Declare

func (c *Consumer[I]) Declare(_ context.Context, ch *amqp.Channel) error

Declare runs ExchangeDeclare / QueueDeclare / QueueBind on the channel as dictated by the configured options. Called by rabbitroutine on every successful (re)connect.

func (*Consumer[I]) SetMarshaller added in v1.3.0

func (c *Consumer[I]) SetMarshaller(m BodyMarshaller[I])

SetMarshaller installs a custom body marshaller. Passing nil resets to the default JSON unmarshaller.

Must be called before the consumer starts processing deliveries (i.e., before the hosting ConsumerHost's Run).

type ConsumerConfig

type ConsumerConfig struct {
	AutoAck   bool
	Exclusive bool
	NoLocal   bool
	NoWait    bool
	Args      amqp.Table
}

ConsumerConfig governs ch.Consume.

type ConsumerHost added in v1.3.0

type ConsumerHost struct {
	// contains filtered or unexported fields
}

ConsumerHost is a Runner that owns a connection and runs a set of rabbitroutine.Consumer instances under it.

Implements lifecycle.Runner.

func NewConsumerHost added in v1.3.0

func NewConsumerHost(cfg Config, opts ...HostOption) *ConsumerHost

NewConsumerHost constructs a ConsumerHost. Use AddConsumer to register consumers before calling Run (typically via app.App).

func (*ConsumerHost) AddConsumer added in v1.3.0

func (h *ConsumerHost) AddConsumer(consumer rabbitroutine.Consumer)

AddConsumer registers a rabbitroutine.Consumer to be started when Run is called. Must be called before Run.

func (*ConsumerHost) Run added in v1.3.0

func (h *ConsumerHost) Run(ctx context.Context) error

Run dials the broker, then starts every registered consumer concurrently. Blocks until ctx is cancelled or any consumer/dial returns an error.

Implements lifecycle.Runner.

func (*ConsumerHost) Shutdown added in v1.3.0

func (h *ConsumerHost) Shutdown(ctx context.Context) error

Shutdown signals Run to stop and waits for it to drain. Implements lifecycle.Resource.

type ConsumerOption added in v1.3.0

type ConsumerOption func(*consumerBase)

ConsumerOption configures a Consumer. Non-generic so option sites don't need to repeat the payload type parameter.

rabbitmq.NewConsumer[OrderEvent]("orders", processor,
    rabbitmq.WithExchange(exchangeCfg),
    rabbitmq.WithQueue(queueCfg),
)

To install a custom body marshaller (which IS type-dependent), use Consumer.SetMarshaller after construction.

func WithBindQueue

func WithBindQueue(c BindQueueConfig) ConsumerOption

WithBindQueue instructs Declare to bind the queue to the exchange.

func WithConsumeOpts

func WithConsumeOpts(c ConsumeOpts) ConsumerOption

WithConsumeOpts overrides QoS prefetch settings.

func WithConsumerConfig

func WithConsumerConfig(c ConsumerConfig) ConsumerOption

WithConsumerConfig overrides the consume-call settings (AutoAck, Exclusive, etc.).

func WithConsumerLogger added in v1.3.0

func WithConsumerLogger(l *slog.Logger) ConsumerOption

WithConsumerLogger attaches a *slog.Logger used for per-message error logging (marshal failures, nack/ack errors). Defaults to slog.Default() when omitted.

func WithExchange

func WithExchange(c ExchangeConfig) ConsumerOption

WithExchange instructs Declare to create an exchange.

func WithQueue

func WithQueue(c QueueConfig) ConsumerOption

WithQueue instructs Declare to create the queue.

func WithWorkerCount added in v1.3.0

func WithWorkerCount(count int) ConsumerOption

WithWorkerCount sets the number of concurrent goroutines processing deliveries for this consumer. Default: 1 (ordered processing — matches the default PrefetchCount=1).

Raising this above 1 gives higher throughput at the cost of losing message order within the queue. Keep ≤ PrefetchCount (ConsumeOpts) or the extra workers starve.

Any value < 1 is normalized to 1.

type ExchangeConfig

type ExchangeConfig struct {
	Name       string
	Kind       ExchangeKind
	Durable    bool
	AutoDelete bool
	Internal   bool
	NoWait     bool
	Args       amqp.Table
}

ExchangeConfig governs ch.ExchangeDeclare.

type ExchangeKind

type ExchangeKind string

ExchangeKind enumerates the AMQP exchange types supported by Consumer.

const (
	ExchangeKindDirect  ExchangeKind = "direct"
	ExchangeKindFanout  ExchangeKind = "fanout"
	ExchangeKindHeaders ExchangeKind = "headers"
	ExchangeKindTopic   ExchangeKind = "topic"
)

type HostOption added in v1.3.0

type HostOption func(*hostOptions)

HostOption configures a ConsumerHost.

func WithHostConnector added in v1.3.0

func WithHostConnector(c ConnectorInterface) HostOption

WithHostConnector lets the caller inject a pre-built connector (e.g., shared with a Publisher).

func WithHostConnectorOptions added in v1.3.0

func WithHostConnectorOptions(opts ...ConnectorOption) HostOption

WithHostConnectorOptions threads ConnectorOption values into the internally-created *Connector when WithHostConnector is not used.

func WithHostLogger added in v1.3.0

func WithHostLogger(l *slog.Logger) HostOption

WithHostLogger attaches a *slog.Logger used for lifecycle events.

type Message

type Message[I any] struct {
	Payload I
}

Message is the typed payload delivered to a Processor.

type Processor

type Processor[I any] interface {
	Process(ctx context.Context, msg Message[I]) error
	GetName() string
}

Processor is what callers implement to handle messages off a queue. The returned error decides whether the underlying delivery is Acked (nil error) or Nacked (non-nil error).

GetName is used as the AMQP consumer tag and shows up in logs / broker admin tools.

type PublishOpts

type PublishOpts struct {
	ContentType string
	MessageID   string
	AppID       string
	UserID      string
	Priority    uint8
	Type        string
}

PublishOpts captures per-publish AMQP message metadata. Body is always JSON-encoded from the message argument; the fields here populate the envelope only.

type Publisher

type Publisher struct {
	// contains filtered or unexported fields
}

Publisher publishes JSON messages to RabbitMQ exchanges with automatic retry/reconnect via rabbitroutine.RetryPublisher.

Publisher is a Resource (not a Runner): the connection's reconnect loop is an internal goroutine started in the constructor and stopped in Shutdown.

Implements lifecycle.Resource.

func NewPublisher added in v1.3.0

func NewPublisher(ctx context.Context, cfg Config, opts ...PublisherOption) (*Publisher, error)

NewPublisher constructs a Publisher and dials the broker. Returns once the connection is established (or fails).

Lifetime of the reconnect loop:

  • The ctx passed here bounds only the INITIAL dial. Once NewPublisher returns successfully, the internal reconnect loop is intentionally detached from that ctx (rooted in context.Background) — otherwise a short-lived startup ctx would kill reconnection during normal operation.
  • To stop the reconnect loop, call Shutdown. This is the ONLY correct way to terminate the Publisher; relying on external ctx cancellation will not stop the loop.

If no connector is injected via WithPublisherConnector, an internal *Connector is built from cfg + WithPublisherConnectorOptions.

func (*Publisher) Publish

func (p *Publisher) Publish(
	ctx context.Context,
	exchange, routingKey string,
	message any,
	opts PublishOpts,
) error

Publish JSON-encodes message and publishes it to exchange with routingKey and the metadata in opts. Wraps rabbitroutine.RetryPublisher.Publish, so transient failures retry up to WithPublishMaxAttempts.

func (*Publisher) Shutdown added in v1.3.0

func (p *Publisher) Shutdown(ctx context.Context) error

Shutdown stops the background reconnect loop and waits for it to drain. Implements lifecycle.Resource.

Safe to call multiple times.

type PublisherOption added in v1.3.0

type PublisherOption func(*publisherOptions)

PublisherOption configures a Publisher.

func WithPublishLinearDelay added in v1.3.0

func WithPublishLinearDelay(d time.Duration) PublisherOption

WithPublishLinearDelay overrides the linear retry delay between publish attempts. Default: 10ms.

func WithPublishMaxAttempts added in v1.3.0

func WithPublishMaxAttempts(n uint) PublisherOption

WithPublishMaxAttempts overrides rabbitroutine.PublishMaxAttemptsSetup. Default: 16.

func WithPublisherConnector added in v1.3.0

func WithPublisherConnector(c ConnectorInterface) PublisherOption

WithPublisherConnector lets callers inject a pre-built ConnectorInterface (e.g., shared between Publisher and ConsumerHost, or a fake in tests). When omitted, NewPublisher constructs its own *Connector from the Config.

func WithPublisherConnectorOptions added in v1.3.0

func WithPublisherConnectorOptions(opts ...ConnectorOption) PublisherOption

WithPublisherConnectorOptions threads ConnectorOption values into the internally-created *Connector when WithPublisherConnector is not used. Ignored when an external connector is supplied.

func WithPublisherLogger added in v1.3.0

func WithPublisherLogger(l *slog.Logger) PublisherOption

WithPublisherLogger attaches a *slog.Logger used for lifecycle events. Defaults to slog.Default() when omitted.

type QueueConfig

type QueueConfig struct {
	Durable    bool
	AutoDelete bool
	Exclusive  bool
	NoWait     bool
	Args       amqp.Table
}

QueueConfig governs ch.QueueDeclare.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL