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 }