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  }