package main import ( "context" "encoding/json" "fmt" "time" "github.com/ThreeDotsLabs/watermill" "github.com/ThreeDotsLabs/watermill-kafka/v3/pkg/kafka" "github.com/ThreeDotsLabs/watermill/message" "github.com/ThreeDotsLabs/watermill/message/router/middleware" "github.com/ThreeDotsLabs/watermill/message/router/plugin" ) var ( brokers = []string{"kafka:9092"} consumeTopic = "events" publishTopic = "events-processed" logger = watermill.NewStdLogger( true, // debug false, // trace ) marshaler = kafka.DefaultMarshaler{} ) type event struct { ID int `json:"id"` } type processedEvent struct { ProcessedID int `json:"processed_id"` Time time.Time `json:"time"` } func main() { publisher := createPublisher() // Subscriber is created with consumer group handler_1 subscriber := createSubscriber("handler_1") router, err := message.NewRouter(message.RouterConfig{}, logger) if err != nil { panic(err) } router.AddPlugin(plugin.SignalsHandler) router.AddMiddleware(middleware.Recoverer) // Adding a handler (multiple handlers can be added) router.AddHandler( "handler_1", // handler name, must be unique consumeTopic, // topic from which messages should be consumed subscriber, publishTopic, // topic to which messages should be published publisher, func(msg *message.Message) ([]*message.Message, error) { consumedPayload := event{} err := json.Unmarshal(msg.Payload, &consumedPayload) if err != nil { // When a handler returns an error, the default behavior is to send a Nack (negative-acknowledgement). // The message will be processed again. // // You can change the default behaviour by using middlewares, like Retry or PoisonQueue. // You can also implement your own middleware. return nil, err } fmt.Printf("received event %+v\n", consumedPayload) newPayload, err := json.Marshal(processedEvent{ ProcessedID: consumedPayload.ID, Time: time.Now(), }) if err != nil { return nil, err } newMessage := message.NewMessage(watermill.NewUUID(), newPayload) return []*message.Message{newMessage}, nil }, ) // Simulate incoming events in the background go simulateEvents(publisher) if err := router.Run(context.Background()); err != nil { panic(err) } } // createPublisher is a helper function that creates a Publisher, in this case - the Kafka Publisher. func createPublisher() message.Publisher { kafkaPublisher, err := kafka.NewPublisher( kafka.PublisherConfig{ Brokers: brokers, Marshaler: marshaler, }, logger, ) if err != nil { panic(err) } return kafkaPublisher } // createSubscriber is a helper function similar to the previous one, but in this case it creates a Subscriber. func createSubscriber(consumerGroup string) message.Subscriber { kafkaSubscriber, err := kafka.NewSubscriber( kafka.SubscriberConfig{ Brokers: brokers, Unmarshaler: marshaler, ConsumerGroup: consumerGroup, // every handler will use a separate consumer group }, logger, ) if err != nil { panic(err) } return kafkaSubscriber } // simulateEvents produces events that will be later consumed. func simulateEvents(publisher message.Publisher) { i := 0 for { e := event{ ID: i, } payload, err := json.Marshal(e) if err != nil { panic(err) } err = publisher.Publish(consumeTopic, message.NewMessage( watermill.NewUUID(), // internal uuid of the message, useful for debugging payload, )) if err != nil { panic(err) } i++ time.Sleep(time.Second) } }