Files
mpc/pkg/messaging/message_queue.go
T

154 lines
3.6 KiB
Go

package messaging
import (
"context"
"errors"
"fmt"
"github.com/cryptoniumX/mpcium/pkg/logger"
"github.com/nats-io/nats.go"
"github.com/nats-io/nats.go/jetstream"
)
var (
ErrPermament = errors.New("Permanent messaging error")
)
type MessageQueue interface {
Enqueue(topic string, message []byte, options *EnqueueOptions) error
Dequeue(topic string, handler func(message []byte) error) error
Close()
}
type EnqueueOptions struct {
IdempotententKey string
}
type msgQueue struct {
consumerName string
js jetstream.JetStream
consumer jetstream.Consumer
consumerContext jetstream.ConsumeContext
}
type NATsMessageQueueManager struct {
queueName string
js jetstream.JetStream
}
func NewNATsMessageQueueManager(queueName string, subjectWildCards []string, nc *nats.Conn) *NATsMessageQueueManager {
js, err := jetstream.New(nc)
if err != nil {
logger.Fatal("Error creating JetStream context: ", err)
}
ctx := context.Background()
stream, err := js.Stream(ctx, queueName)
if err != nil {
logger.Warn("Stream not found, creating new stream", "stream", queueName)
}
if stream != nil {
info, _ := stream.Info(ctx)
logger.Info("Stream found", "info", info)
}
_, err = js.CreateOrUpdateStream(context.Background(), jetstream.StreamConfig{
Name: queueName,
Description: "Stream for " + queueName,
Subjects: subjectWildCards,
MaxBytes: 1024,
Storage: jetstream.FileStorage,
Retention: jetstream.WorkQueuePolicy,
})
if err != nil {
logger.Fatal("Error creating JetStream stream: ", err)
}
logger.Info("Creating apex NATs Jetstream context successfully!")
return &NATsMessageQueueManager{
queueName: queueName,
js: js,
}
}
func (m *NATsMessageQueueManager) NewMessageQueue(consumerName string) MessageQueue {
mq := &msgQueue{
consumerName: consumerName,
js: m.js,
}
consumerWildCard := fmt.Sprintf("%s.%s.*", m.queueName, consumerName)
cfg := jetstream.ConsumerConfig{
Name: consumerName,
Durable: consumerName,
MaxAckPending: 4,
FilterSubjects: []string{
consumerWildCard,
},
MaxDeliver: 3,
}
logger.Info("Creating consumer for subject", "config", cfg)
consumer, err := m.js.CreateOrUpdateConsumer(context.Background(), m.queueName, cfg)
if err != nil {
logger.Fatal("Error creating JetStream consumer: ", err)
}
mq.consumer = consumer
return mq
}
func (mq *msgQueue) Enqueue(topic string, message []byte, options *EnqueueOptions) error {
header := nats.Header{}
if options != nil {
header.Add("Nats-Msg-Id", options.IdempotententKey)
}
logger.Info("Publishing message", "topic", topic)
_, err := mq.js.PublishMsg(context.Background(), &nats.Msg{
Subject: topic,
Data: message,
Header: header,
})
if err != nil {
return fmt.Errorf("Error enqueueing message: %w", err)
}
return nil
}
func (mq *msgQueue) Dequeue(topic string, handler func(message []byte) error) error {
c, err := mq.consumer.Consume(func(msg jetstream.Msg) {
meta, _ := msg.Metadata()
logger.Info("Received message", "meta", meta)
err := handler(msg.Data())
if err != nil {
if errors.Is(err, ErrPermament) {
logger.Info("Permanent error on message", "meta", meta)
msg.Term()
return
}
logger.Error("Error handling message: ", err)
msg.Nak()
return
}
logger.Info("Message Acknowledged", "meta", meta)
err = msg.Ack()
if err != nil {
logger.Error("Error acknowledging message: ", err)
}
})
mq.consumerContext = c
return err
}
func (mq *msgQueue) Close() {
mq.consumerContext.Stop()
}
func (n *msgQueue) handleReconnect(nc *nats.Conn) {
logger.Info("NATS: Reconnected to NATS")
}