github.com/wfusion/gofusion@v1.1.14/common/infra/watermill/components/forwarder/forwarder.go (about) 1 package forwarder 2 3 import ( 4 "context" 5 "time" 6 7 "github.com/pkg/errors" 8 9 "github.com/wfusion/gofusion/common/infra/watermill" 10 "github.com/wfusion/gofusion/common/infra/watermill/message" 11 ) 12 13 const defaultForwarderTopic = "forwarder_topic" 14 15 type Config struct { 16 // ForwarderTopic is a topic on which the forwarder will be listening to enveloped messages to forward. 17 // Defaults to `forwarder_topic`. 18 ForwarderTopic string 19 20 // Middlewares are used to decorate forwarder's handler function. 21 Middlewares []message.HandlerMiddleware 22 23 // CloseTimeout determines how long router should work for handlers when closing. 24 CloseTimeout time.Duration 25 26 // AckWhenCannotUnwrap enables acking of messages which cannot be unwrapped from an envelope. 27 AckWhenCannotUnwrap bool 28 29 // Router is a router used by the forwarder. 30 // If not provided, a new router will be created. 31 // 32 // If router is provided, it's not necessary to call `Forwarder.Run()` if the router is started with `router.Run()`. 33 Router *message.Router 34 } 35 36 func (c *Config) setDefaults() { 37 if c.CloseTimeout == 0 { 38 c.CloseTimeout = time.Second * 30 39 } 40 if c.ForwarderTopic == "" { 41 c.ForwarderTopic = defaultForwarderTopic 42 } 43 } 44 45 func (c *Config) Validate() error { 46 if c.ForwarderTopic == "" { 47 return errors.New("empty forwarder topic") 48 } 49 50 return nil 51 } 52 53 // Forwarder subscribes to the topic provided in the config and publishes them to the destination topic embedded in the enveloped message. 54 type Forwarder struct { 55 router *message.Router 56 publisher message.Publisher 57 logger watermill.LoggerAdapter 58 config Config 59 } 60 61 // NewForwarder creates a forwarder which will subscribe to the topic provided in the config using the provided subscriber. 62 // It will publish messages received on this subscription to the destination topic embedded in the enveloped message using the provided publisher. 63 // 64 // Provided subscriber and publisher can be from different Watermill Pub/Sub implementations, i.e. MySQL subscriber and Google Pub/Sub publisher. 65 // 66 // Note: Keep in mind that by default the forwarder will nack all messages which weren't sent using a decorated publisher. 67 // You can change this behavior by passing a middleware which will ack them instead. 68 func NewForwarder(subscriberIn message.Subscriber, publisherOut message.Publisher, 69 logger watermill.LoggerAdapter, config Config) (*Forwarder, error) { 70 config.setDefaults() 71 72 routerConfig := message.RouterConfig{CloseTimeout: config.CloseTimeout} 73 if err := routerConfig.Validate(); err != nil { 74 return nil, errors.Wrap(err, "invalid router config") 75 } 76 77 var router *message.Router 78 if config.Router != nil { 79 router = config.Router 80 } else { 81 var err error 82 router, err = message.NewRouter(routerConfig, logger) 83 if err != nil { 84 return nil, errors.Wrap(err, "cannot create a router") 85 } 86 } 87 88 f := &Forwarder{router, publisherOut, logger, config} 89 90 handler := router.AddNoPublisherHandler( 91 "events_forwarder", 92 config.ForwarderTopic, 93 subscriberIn, 94 f.forwardMessage, 95 ) 96 97 handler.AddMiddleware(config.Middlewares...) 98 99 return f, nil 100 } 101 102 // Run runs forwarder's handler responsible for forwarding messages. 103 // This call is blocking while the forwarder is running. 104 // ctx will be propagated to the forwarder's subscription. 105 // 106 // To stop Run() you should call Close() on the forwarder. 107 func (f *Forwarder) Run(ctx context.Context) error { 108 return f.router.Run(ctx) 109 } 110 111 // Close stops forwarder's handler. 112 func (f *Forwarder) Close() error { 113 return f.router.Close() 114 } 115 116 // Running returns channel which is closed when the forwarder is running. 117 func (f *Forwarder) Running() chan struct{} { 118 return f.router.Running() 119 } 120 121 func (f *Forwarder) forwardMessage(msg *message.Message) error { 122 destTopic, unwrappedMsg, err := unwrapMessageFromEnvelope(msg) 123 if err != nil { 124 f.logger.Error("Could not unwrap a message from an envelope", err, watermill.LogFields{ 125 "uuid": msg.UUID, 126 "payload": msg.Payload, 127 "metadata": msg.Metadata, 128 "acked": f.config.AckWhenCannotUnwrap, 129 }) 130 131 if f.config.AckWhenCannotUnwrap { 132 return nil 133 } 134 return errors.Wrap(err, "cannot unwrap message from an envelope") 135 } 136 137 if err := f.publisher.Publish(context.Background(), destTopic, unwrappedMsg); err != nil { 138 return errors.Wrap(err, "cannot publish a message") 139 } 140 141 return nil 142 }