github.com/wfusion/gofusion@v1.1.14/common/infra/watermill/pubsub/kafka/publisher.go (about)

     1  package kafka
     2  
     3  import (
     4  	"context"
     5  	"time"
     6  
     7  	"github.com/IBM/sarama"
     8  	"github.com/pkg/errors"
     9  
    10  	"github.com/wfusion/gofusion/common/infra/watermill"
    11  	"github.com/wfusion/gofusion/common/infra/watermill/message"
    12  )
    13  
    14  type Publisher struct {
    15  	config   PublisherConfig
    16  	producer sarama.SyncProducer
    17  	logger   watermill.LoggerAdapter
    18  
    19  	closed bool
    20  }
    21  
    22  // NewPublisher creates a new Kafka Publisher.
    23  func NewPublisher(
    24  	config PublisherConfig,
    25  	logger watermill.LoggerAdapter,
    26  ) (*Publisher, error) {
    27  	config.setDefaults()
    28  
    29  	if err := config.Validate(); err != nil {
    30  		return nil, err
    31  	}
    32  
    33  	if logger == nil {
    34  		logger = watermill.NopLogger{}
    35  	}
    36  
    37  	producer, err := sarama.NewSyncProducer(config.Brokers, config.OverwriteSaramaConfig)
    38  	if err != nil {
    39  		return nil, errors.Wrap(err, "cannot create Kafka producer")
    40  	}
    41  
    42  	return &Publisher{
    43  		config:   config,
    44  		producer: producer,
    45  		logger:   logger,
    46  	}, nil
    47  }
    48  
    49  type PublisherConfig struct {
    50  	// Kafka brokers list.
    51  	Brokers []string
    52  
    53  	// Marshaler is used to marshal messages from Watermill format into Kafka format.
    54  	Marshaler Marshaler
    55  
    56  	// OverwriteSaramaConfig holds additional sarama settings.
    57  	OverwriteSaramaConfig *sarama.Config
    58  
    59  	// If true then each sent message will be wrapped with Opentelemetry tracing, provided by otelsarama.
    60  	// OTELEnabled bool
    61  
    62  	// Tracer is used to trace Kafka messages.
    63  	// If nil, then no tracing will be used.
    64  	// Tracer SaramaTracer
    65  }
    66  
    67  func (c *PublisherConfig) setDefaults() {
    68  	if c.OverwriteSaramaConfig == nil {
    69  		c.OverwriteSaramaConfig = DefaultSaramaSyncPublisherConfig()
    70  	}
    71  }
    72  
    73  func (c PublisherConfig) Validate() error {
    74  	if len(c.Brokers) == 0 {
    75  		return errors.New("missing brokers")
    76  	}
    77  	if c.Marshaler == nil {
    78  		return errors.New("missing marshaler")
    79  	}
    80  
    81  	return nil
    82  }
    83  
    84  func DefaultSaramaSyncPublisherConfig() *sarama.Config {
    85  	config := sarama.NewConfig()
    86  
    87  	config.Producer.Retry.Max = 10
    88  	config.Producer.Return.Successes = true
    89  	config.Version = sarama.V1_0_0_0
    90  	config.Metadata.Retry.Backoff = time.Second * 2
    91  	config.ClientID = "watermill"
    92  
    93  	return config
    94  }
    95  
    96  // Publish publishes message to Kafka.
    97  //
    98  // Publish is blocking and wait for ack from Kafka.
    99  // When one of messages delivery fails - function is interrupted.
   100  func (p *Publisher) Publish(ctx context.Context, topic string, msgs ...*message.Message) error {
   101  	if p.closed {
   102  		return errors.New("publisher closed")
   103  	}
   104  
   105  	logFields := make(watermill.LogFields, 4)
   106  	logFields["topic"] = topic
   107  
   108  	for _, msg := range msgs {
   109  		logFields["message_uuid"] = msg.UUID
   110  		p.logger.Trace("[Common] watermill sending message to kafka", logFields)
   111  
   112  		kafkaMsg, err := p.config.Marshaler.Marshal(topic, msg)
   113  		if err != nil {
   114  			return errors.Wrapf(err, "cannot marshal message %s", msg.UUID)
   115  		}
   116  
   117  		partition, offset, err := p.producer.SendMessage(kafkaMsg)
   118  		if err != nil {
   119  			return errors.Wrapf(err, "cannot produce message %s", msg.UUID)
   120  		}
   121  
   122  		logFields["kafka_partition"] = partition
   123  		logFields["kafka_partition_offset"] = offset
   124  
   125  		p.logger.Trace("[Common] watermill message sent to kafka", logFields)
   126  	}
   127  
   128  	return nil
   129  }
   130  
   131  func (p *Publisher) Close() error {
   132  	if p.closed {
   133  		return nil
   134  	}
   135  	p.closed = true
   136  
   137  	if err := p.producer.Close(); err != nil {
   138  		return errors.Wrap(err, "cannot close Kafka producer")
   139  	}
   140  
   141  	return nil
   142  }