snapshot of https://github.com/ThreeDotsLabs/watermill.git (master, 2026-09-15; blobs > 131072B omitted)
Some checks failed
master / ci (push) Failing after 0s
Some checks failed
master / ci (push) Failing after 0s
This commit is contained in:
169
components/forwarder/forwarder.go
Normal file
169
components/forwarder/forwarder.go
Normal file
@@ -0,0 +1,169 @@
|
||||
package forwarder
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/ThreeDotsLabs/watermill"
|
||||
"github.com/ThreeDotsLabs/watermill/message"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
const (
|
||||
defaultForwarderTopic = "forwarder_topic"
|
||||
defaultHandlerName = "events_forwarder"
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
// ForwarderTopic is a topic on which the forwarder will be listening to enveloped messages to forward.
|
||||
// Defaults to `forwarder_topic`.
|
||||
ForwarderTopic string
|
||||
|
||||
// HandlerName is the name of the forwarder's handler registered in the router.
|
||||
// Defaults to `events_forwarder`.
|
||||
//
|
||||
// Handler names must be unique within a router, so it needs to be set explicitly
|
||||
// if you want to run multiple forwarders on the same router.
|
||||
HandlerName string
|
||||
|
||||
// Middlewares are used to decorate forwarder's handler function.
|
||||
Middlewares []message.HandlerMiddleware
|
||||
|
||||
// CloseTimeout determines how long router should work for handlers when closing.
|
||||
CloseTimeout time.Duration
|
||||
|
||||
// AckWhenCannotUnwrap enables acking of messages which cannot be unwrapped from an envelope.
|
||||
AckWhenCannotUnwrap bool
|
||||
|
||||
// Router is a router used by the forwarder.
|
||||
// If not provided, a new router will be created.
|
||||
//
|
||||
// If router is provided, it's not necessary to call `Forwarder.Run()` if the router is started with `router.Run()`.
|
||||
Router *message.Router
|
||||
|
||||
// Marshaler is used to deserialize envelopes received on ForwarderTopic.
|
||||
// It must match the Marshaler used by the decorated Publisher.
|
||||
// Defaults to DefaultMarshaler, which uses encoding/json.
|
||||
Marshaler Marshaler
|
||||
}
|
||||
|
||||
func (c *Config) setDefaults() {
|
||||
if c.CloseTimeout == 0 {
|
||||
c.CloseTimeout = time.Second * 30
|
||||
}
|
||||
if c.ForwarderTopic == "" {
|
||||
c.ForwarderTopic = defaultForwarderTopic
|
||||
}
|
||||
if c.HandlerName == "" {
|
||||
c.HandlerName = defaultHandlerName
|
||||
}
|
||||
if c.Marshaler == nil {
|
||||
c.Marshaler = DefaultMarshaler{}
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Config) Validate() error {
|
||||
if c.ForwarderTopic == "" {
|
||||
return errors.New("empty forwarder topic")
|
||||
}
|
||||
if c.HandlerName == "" {
|
||||
return errors.New("empty handler name")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Forwarder subscribes to the topic provided in the config and publishes them to the destination topic embedded in the enveloped message.
|
||||
type Forwarder struct {
|
||||
router *message.Router
|
||||
publisher message.Publisher
|
||||
logger watermill.LoggerAdapter
|
||||
config Config
|
||||
}
|
||||
|
||||
// NewForwarder creates a forwarder which will subscribe to the topic provided in the config using the provided subscriber.
|
||||
// It will publish messages received on this subscription to the destination topic embedded in the enveloped message using the provided publisher.
|
||||
//
|
||||
// Provided subscriber and publisher can be from different Watermill Pub/Sub implementations, i.e. MySQL subscriber and Google Pub/Sub publisher.
|
||||
//
|
||||
// Note: Keep in mind that by default the forwarder will nack all messages which weren't sent using a decorated publisher.
|
||||
// You can change this behavior by passing a middleware which will ack them instead.
|
||||
func NewForwarder(
|
||||
subscriberIn message.Subscriber,
|
||||
publisherOut message.Publisher,
|
||||
logger watermill.LoggerAdapter,
|
||||
config Config,
|
||||
) (*Forwarder, error) {
|
||||
config.setDefaults()
|
||||
|
||||
routerConfig := message.RouterConfig{CloseTimeout: config.CloseTimeout}
|
||||
if err := routerConfig.Validate(); err != nil {
|
||||
return nil, errors.Wrap(err, "invalid router config")
|
||||
}
|
||||
|
||||
var router *message.Router
|
||||
if config.Router != nil {
|
||||
router = config.Router
|
||||
} else {
|
||||
var err error
|
||||
router, err = message.NewRouter(routerConfig, logger)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "cannot create a router")
|
||||
}
|
||||
}
|
||||
|
||||
f := &Forwarder{router, publisherOut, logger, config}
|
||||
|
||||
handler := router.AddConsumerHandler(
|
||||
config.HandlerName,
|
||||
config.ForwarderTopic,
|
||||
subscriberIn,
|
||||
f.forwardMessage,
|
||||
)
|
||||
|
||||
handler.AddMiddleware(config.Middlewares...)
|
||||
|
||||
return f, nil
|
||||
}
|
||||
|
||||
// Run runs forwarder's handler responsible for forwarding messages.
|
||||
// This call is blocking while the forwarder is running.
|
||||
// ctx will be propagated to the forwarder's subscription.
|
||||
//
|
||||
// To stop Run() you should call Close() on the forwarder.
|
||||
func (f *Forwarder) Run(ctx context.Context) error {
|
||||
return f.router.Run(ctx)
|
||||
}
|
||||
|
||||
// Close stops forwarder's handler.
|
||||
func (f *Forwarder) Close() error {
|
||||
return f.router.Close()
|
||||
}
|
||||
|
||||
// Running returns channel which is closed when the forwarder is running.
|
||||
func (f *Forwarder) Running() chan struct{} {
|
||||
return f.router.Running()
|
||||
}
|
||||
|
||||
func (f *Forwarder) forwardMessage(msg *message.Message) error {
|
||||
destTopic, unwrappedMsg, err := unwrapMessageFromEnvelope(msg, f.config.Marshaler)
|
||||
if err != nil {
|
||||
f.logger.Error("Could not unwrap a message from an envelope", err, watermill.LogFields{
|
||||
"uuid": msg.UUID,
|
||||
"payload": msg.Payload,
|
||||
"metadata": msg.Metadata,
|
||||
"acked": f.config.AckWhenCannotUnwrap,
|
||||
})
|
||||
|
||||
if f.config.AckWhenCannotUnwrap {
|
||||
return nil
|
||||
}
|
||||
return errors.Wrap(err, "cannot unwrap message from an envelope")
|
||||
}
|
||||
|
||||
if err := f.publisher.Publish(destTopic, unwrappedMsg); err != nil {
|
||||
return errors.Wrap(err, "cannot publish a message")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
Reference in New Issue
Block a user