Files
lndhub.go/rabbitmq/amqp.go
2023-05-29 16:46:43 +02:00

196 lines
4.9 KiB
Go

package rabbitmq
import (
"context"
amqp "github.com/rabbitmq/amqp091-go"
)
type AMQPClient interface {
Listen(ctx context.Context, exchange string, routingKey string, queueName string, options ...AMQPListenOptions) (<-chan amqp.Delivery, error)
PublishWithContext(ctx context.Context, exchange, key string, mandatory, immediate bool, msg amqp.Publishing) error
ExchangeDeclare(name, kind string, durable, autoDelete, internal, noWait bool, args amqp.Table) error
Close() error
}
type DefaultAMQPCLient struct {
conn *amqp.Connection
// It is recommended that, when possible, publishers and consumers
// use separate connections so that consumers are isolated from potential
// flow control measures that may be applied to publishing connections.
consumeChannel *amqp.Channel
publishChannel *amqp.Channel
}
func (c *DefaultAMQPCLient) Close() error { return c.conn.Close() }
func (c *DefaultAMQPCLient) ExchangeDeclare(name, kind string, durable, autoDelete, internal, noWait bool, args amqp.Table) error {
// TODO: Seperate management channel? Or provide way to select channel?
ch, err := c.conn.Channel()
if err != nil {
return err
}
defer ch.Close()
return ch.ExchangeDeclare(name, kind, durable, autoDelete, internal, noWait, args)
}
type listenOptions struct {
Durable bool
AutoDelete bool
Internal bool
Wait bool
Exclusive bool
AutoAck bool
}
type AMQPListenOptions = func(opts listenOptions) listenOptions
func WithDurable(durable bool) AMQPListenOptions {
return func(opts listenOptions) listenOptions {
opts.Durable = durable
return opts
}
}
func WithAutoDelete(autoDelete bool) AMQPListenOptions {
return func(opts listenOptions) listenOptions {
opts.AutoDelete = autoDelete
return opts
}
}
func WithInternal(internal bool) AMQPListenOptions {
return func(opts listenOptions) listenOptions {
opts.Internal = internal
return opts
}
}
func WithWait(wait bool) AMQPListenOptions {
return func(opts listenOptions) listenOptions {
opts.Wait = wait
return opts
}
}
func WithExclusive(exclusive bool) AMQPListenOptions {
return func(opts listenOptions) listenOptions {
opts.Exclusive = exclusive
return opts
}
}
func WithAutoAck(autoAck bool) AMQPListenOptions {
return func(opts listenOptions) listenOptions {
opts.AutoAck = autoAck
return opts
}
}
func (c *DefaultAMQPCLient) Listen(ctx context.Context, exchange string, routingKey string, queueName string, options ...AMQPListenOptions) (<-chan amqp.Delivery, error) {
opts := listenOptions{
Durable: true,
AutoDelete: false,
Internal: false,
Wait: false,
Exclusive: false,
AutoAck: false,
}
for _, opt := range options {
opts = opt(opts)
}
err := c.consumeChannel.ExchangeDeclare(
exchange,
// topic is a type of exchange that allows routing messages to different queue's bases on a routing key
"topic",
// Durable and Non-Auto-Deleted exchanges will survive server restarts and remain
// declared when there are no remaining bindings.
opts.Durable,
opts.AutoDelete,
// Non-Internal exchange's accept direct publishing
opts.Internal,
// Nowait: We set this to false as we want to wait for a server response
// to check whether the exchange was created succesfully
opts.Wait,
nil,
)
if err != nil {
return nil, err
}
queue, err := c.consumeChannel.QueueDeclare(
queueName,
// Durable and Non-Auto-Deleted queues will survive server restarts and remain
// declared when there are no remaining bindings.
opts.Durable,
opts.AutoDelete,
// None-Exclusive means other consumers can consume from this queue.
// Messages from queues are spread out and load balanced between consumers.
// So multiple lndhub.go instances will spread the load of invoices between them
opts.Exclusive,
// Nowait: We set this to false as we want to wait for a server response
// to check whether the queue was created successfully
opts.Wait,
nil,
)
if err != nil {
return nil, err
}
err = c.consumeChannel.QueueBind(
queue.Name,
routingKey,
exchange,
// Nowait: We set this to false as we want to wait for a server response
// to check whether the queue was created successfully
opts.Wait,
nil,
)
if err != nil {
return nil, err
}
return c.consumeChannel.Consume(
queue.Name,
"",
opts.AutoAck,
opts.Exclusive,
false,
opts.Wait,
nil,
)
}
func (c *DefaultAMQPCLient) PublishWithContext(ctx context.Context, exchange string, key string, mandatory bool, immediate bool, msg amqp.Publishing) error {
return c.publishChannel.PublishWithContext(ctx, exchange, key, mandatory, immediate, msg)
}
func Dial(uri string) (AMQPClient, error) {
conn, err := amqp.Dial(uri)
if err != nil {
return nil, err
}
consumeChannel, err := conn.Channel()
if err != nil {
return nil, err
}
publishChannel, err := conn.Channel()
if err != nil {
return nil, err
}
return &DefaultAMQPCLient{
conn,
consumeChannel,
publishChannel,
}, nil
}