github.com/wfusion/gofusion@v1.1.14/common/infra/watermill/pubsub/amqp/config.go (about)

     1  package amqp
     2  
     3  import (
     4  	"crypto/tls"
     5  	"time"
     6  
     7  	"github.com/cenkalti/backoff/v4"
     8  	"github.com/pkg/errors"
     9  	"go.uber.org/multierr"
    10  
    11  	amqp "github.com/rabbitmq/amqp091-go"
    12  )
    13  
    14  // NewDurablePubSubConfig creates config for durable PubSub.
    15  // generateQueueName is optional, when passing to the publisher.
    16  // Exchange name is set to the topic name and routing key is empty.
    17  //
    18  // IMPORTANT: Watermill's topic is not mapped directly to the AMQP's topic exchange type.
    19  // It is used to generate exchange name, routing key and queue name, depending on the context.
    20  // To check how topic is mapped, please check Exchange.GenerateName, Queue.GenerateName and Publish.GenerateRoutingKey.
    21  //
    22  // This config is based on this example: https://www.rabbitmq.com/tutorials/tutorial-three-go.html
    23  // with durable added for exchange, queue and amqp.Persistent DeliveryMode.
    24  // Thanks to this, we don't lose messages on broker restart.
    25  func NewDurablePubSubConfig(amqpURI string, generateQueueName QueueNameGenerator) Config {
    26  	return Config{
    27  		Connection: ConnectionConfig{
    28  			AmqpURI: amqpURI,
    29  		},
    30  
    31  		Marshaler: DefaultMarshaler{},
    32  
    33  		Exchange: ExchangeConfig{
    34  			GenerateName: func(topic string) string {
    35  				return topic
    36  			},
    37  			Type:    "fanout",
    38  			Durable: true,
    39  		},
    40  		Queue: QueueConfig{
    41  			GenerateName: generateQueueName,
    42  			Durable:      true,
    43  		},
    44  		QueueBind: QueueBindConfig{
    45  			GenerateRoutingKey: func(topic string) string {
    46  				return ""
    47  			},
    48  		},
    49  		Publish: PublishConfig{
    50  			GenerateRoutingKey: func(topic string) string {
    51  				return ""
    52  			},
    53  		},
    54  		Consume: ConsumeConfig{
    55  			Qos: QosConfig{
    56  				PrefetchCount: 1,
    57  			},
    58  		},
    59  		TopologyBuilder: &DefaultTopologyBuilder{},
    60  	}
    61  }
    62  
    63  // NewNonDurablePubSubConfig creates config for non durable PubSub.
    64  // generateQueueName is optional, when passing to the publisher.
    65  // Exchange name is set to the topic name and routing key is empty.
    66  //
    67  // IMPORTANT: Watermill's topic is not mapped directly to the AMQP's topic exchange type.
    68  // It is used to generate exchange name, routing key and queue name, depending on the context.
    69  // To check how topic is mapped, please check Exchange.GenerateName, Queue.GenerateName and Publish.GenerateRoutingKey.
    70  //
    71  // This config is based on this example: https://www.rabbitmq.com/tutorials/tutorial-three-go.html.
    72  // This config is not durable, so on the restart of the broker all messages will be lost.
    73  func NewNonDurablePubSubConfig(amqpURI string, generateQueueName QueueNameGenerator) Config {
    74  	return Config{
    75  		Connection: ConnectionConfig{
    76  			AmqpURI: amqpURI,
    77  		},
    78  
    79  		Marshaler: DefaultMarshaler{NotPersistentDeliveryMode: true},
    80  
    81  		Exchange: ExchangeConfig{
    82  			GenerateName: func(topic string) string {
    83  				return topic
    84  			},
    85  			Type: "fanout",
    86  		},
    87  		Queue: QueueConfig{
    88  			GenerateName: generateQueueName,
    89  		},
    90  		QueueBind: QueueBindConfig{
    91  			GenerateRoutingKey: func(topic string) string {
    92  				return ""
    93  			},
    94  		},
    95  		Publish: PublishConfig{
    96  			GenerateRoutingKey: func(topic string) string {
    97  				return ""
    98  			},
    99  		},
   100  		Consume: ConsumeConfig{
   101  			Qos: QosConfig{
   102  				PrefetchCount: 1,
   103  			},
   104  		},
   105  		TopologyBuilder: &DefaultTopologyBuilder{},
   106  	}
   107  }
   108  
   109  // NewDurableQueueConfig creates config for durable Queue.
   110  // Queue name and routing key is set to the topic name by default. Default ("") exchange is used.
   111  //
   112  // IMPORTANT: Watermill's topic is not mapped directly to the AMQP's topic exchange type.
   113  // It is used to generate exchange name, routing key and queue name, depending on the context.
   114  // To check how topic is mapped, please check Exchange.GenerateName, Queue.GenerateName and Publish.GenerateRoutingKey.
   115  //
   116  // This config is based on this example: https://www.rabbitmq.com/tutorials/tutorial-two-go.html
   117  // with durable added for exchange, queue and amqp.Persistent DeliveryMode.
   118  // Thanks to this, we don't lose messages on broker restart.
   119  func NewDurableQueueConfig(amqpURI string) Config {
   120  	return Config{
   121  		Connection: ConnectionConfig{
   122  			AmqpURI: amqpURI,
   123  		},
   124  
   125  		Marshaler: DefaultMarshaler{},
   126  
   127  		Exchange: ExchangeConfig{
   128  			GenerateName: func(topic string) string {
   129  				return ""
   130  			},
   131  		},
   132  		Queue: QueueConfig{
   133  			GenerateName: GenerateQueueNameTopicName,
   134  			Durable:      true,
   135  		},
   136  		QueueBind: QueueBindConfig{
   137  			GenerateRoutingKey: func(topic string) string {
   138  				return ""
   139  			},
   140  		},
   141  		Publish: PublishConfig{
   142  			GenerateRoutingKey: func(topic string) string {
   143  				return topic
   144  			},
   145  		},
   146  		Consume: ConsumeConfig{
   147  			Qos: QosConfig{
   148  				PrefetchCount: 1,
   149  			},
   150  		},
   151  		TopologyBuilder: &DefaultTopologyBuilder{},
   152  	}
   153  }
   154  
   155  // NewNonDurableQueueConfig creates config for non durable Queue.
   156  // Queue name and routing key is set to the topic name by default. Default ("") exchange is used.
   157  //
   158  // IMPORTANT: Watermill's topic is not mapped directly to the AMQP's topic exchange type.
   159  // It is used to generate exchange name, routing key and queue name, depending on the context.
   160  // To check how topic is mapped, please check Exchange.GenerateName, Queue.GenerateName and Publish.GenerateRoutingKey.
   161  //
   162  // This config is based on this example: https://www.rabbitmq.com/tutorials/tutorial-two-go.html.
   163  // This config is not durable, so on the restart of the broker all messages will be lost.
   164  func NewNonDurableQueueConfig(amqpURI string) Config {
   165  	return Config{
   166  		Connection: ConnectionConfig{
   167  			AmqpURI: amqpURI,
   168  		},
   169  
   170  		Marshaler: DefaultMarshaler{NotPersistentDeliveryMode: true},
   171  
   172  		Exchange: ExchangeConfig{
   173  			GenerateName: func(topic string) string {
   174  				return ""
   175  			},
   176  		},
   177  		Queue: QueueConfig{
   178  			GenerateName: GenerateQueueNameTopicName,
   179  		},
   180  		QueueBind: QueueBindConfig{
   181  			GenerateRoutingKey: func(topic string) string {
   182  				return ""
   183  			},
   184  		},
   185  		Publish: PublishConfig{
   186  			GenerateRoutingKey: func(topic string) string {
   187  				return topic
   188  			},
   189  		},
   190  		Consume: ConsumeConfig{
   191  			Qos: QosConfig{
   192  				PrefetchCount: 1,
   193  			},
   194  		},
   195  		TopologyBuilder: &DefaultTopologyBuilder{},
   196  	}
   197  }
   198  
   199  type Config struct {
   200  	Connection ConnectionConfig
   201  
   202  	Marshaler Marshaler
   203  
   204  	Exchange  ExchangeConfig
   205  	Queue     QueueConfig
   206  	QueueBind QueueBindConfig
   207  
   208  	Publish PublishConfig
   209  	Consume ConsumeConfig
   210  
   211  	TopologyBuilder TopologyBuilder
   212  }
   213  
   214  func (c Config) validate(validateConnection bool) error {
   215  	var err error
   216  
   217  	if validateConnection {
   218  		if c.Connection.AmqpURI == "" {
   219  			err = multierr.Append(err, errors.New("empty Config.AmqpURI"))
   220  		}
   221  	}
   222  	if c.Marshaler == nil {
   223  		err = multierr.Append(err, errors.New("missing Config.Marshaler"))
   224  	}
   225  	if c.Exchange.GenerateName == nil {
   226  		err = multierr.Append(err, errors.New("missing Config.GenerateName"))
   227  	}
   228  
   229  	return err
   230  }
   231  
   232  func (c Config) validatePublisher(validateConnection bool) error {
   233  	err := c.validate(validateConnection)
   234  
   235  	if c.Publish.GenerateRoutingKey == nil {
   236  		err = multierr.Append(err, errors.New("missing Config.GenerateRoutingKey"))
   237  	}
   238  
   239  	return err
   240  }
   241  
   242  func (c Config) validateSubscriber(validateConnection bool) error {
   243  	err := c.validate(validateConnection)
   244  
   245  	if c.Queue.GenerateName == nil {
   246  		err = multierr.Append(err, errors.New("missing Config.Queue.GenerateName"))
   247  	}
   248  
   249  	return err
   250  }
   251  
   252  func (c Config) ValidatePublisher() error {
   253  	return c.validatePublisher(true)
   254  }
   255  
   256  func (c Config) ValidatePublisherWithConnection() error {
   257  	return c.validatePublisher(false)
   258  }
   259  
   260  func (c Config) ValidateSubscriber() error {
   261  	return c.validateSubscriber(true)
   262  }
   263  
   264  func (c Config) ValidateSubscriberWithConnection() error {
   265  	return c.validateSubscriber(false)
   266  }
   267  
   268  type ConnectionConfig struct {
   269  	AmqpURI string
   270  
   271  	TLSConfig  *tls.Config
   272  	AmqpConfig *amqp.Config
   273  
   274  	Reconnect *ReconnectConfig
   275  }
   276  
   277  // Config descriptions are based on descriptions from: https://github.com/streadway/amqp
   278  // Copyright (c) 2012, Sean Treadway, SoundCloud Ltd.
   279  // BSD 2-Clause "Simplified" License
   280  
   281  type ExchangeConfig struct {
   282  	// GenerateName is generated based on the topic provided for Publish or Subscribe method.
   283  	//
   284  	// Exchange names starting with "amq." are reserved for pre-declared and
   285  	// standardized exchanges. The client MAY declare an exchange starting with
   286  	// "amq." if the passive option is set, or the exchange already exists.  Names can
   287  	// consist of a non-empty sequence of letters, digits, hyphen, underscore,
   288  	// period, or colon.
   289  	GenerateName func(topic string) string
   290  
   291  	// Each exchange belongs to one of a set of exchange kinds/types implemented by
   292  	// the server. The exchange types define the functionality of the exchange - i.e.
   293  	// how messages are routed through it. Once an exchange is declared, its type
   294  	// cannot be changed.  The common types are "direct", "fanout", "topic" and
   295  	// "headers".
   296  	Type string
   297  
   298  	// Durable and Non-Auto-Deleted exchanges will survive server restarts and remain
   299  	// declared when there are no remaining bindings.  This is the best lifetime for
   300  	// long-lived exchange configurations like stable routes and default exchanges.
   301  	Durable bool
   302  
   303  	// Non-Durable and Auto-Deleted exchanges will be deleted when there are no
   304  	// remaining bindings and not restored on server restart.  This lifetime is
   305  	// useful for temporary topologies that should not pollute the virtual host on
   306  	// failure or after the consumers have completed.
   307  	//
   308  	// Non-Durable and Non-Auto-deleted exchanges will remain as long as the server is
   309  	// running including when there are no remaining bindings.  This is useful for
   310  	// temporary topologies that may have long delays between bindings.
   311  	//
   312  	AutoDeleted bool
   313  
   314  	// Exchanges declared as `internal` do not accept accept publishings. Internal
   315  	// exchanges are useful when you wish to implement inter-exchange topologies
   316  	// that should not be exposed to users of the broker.
   317  	Internal bool
   318  
   319  	// When noWait is true, declare without waiting for a confirmation from the server.
   320  	// The channel may be closed as a result of an error.  Add a NotifyClose listener-
   321  	// to respond to any exceptions.
   322  	NoWait bool
   323  
   324  	// Optional amqp.Table of arguments that are specific to the server's implementation of
   325  	// the exchange can be sent for exchange types that require extra parameters.
   326  	Arguments amqp.Table
   327  }
   328  
   329  // QueueNameGenerator generates QueueName based on the topic.
   330  type QueueNameGenerator func(topic string) string
   331  
   332  // GenerateQueueNameTopicName generates queueName equal to the topic.
   333  func GenerateQueueNameTopicName(topic string) string {
   334  	return topic
   335  }
   336  
   337  // GenerateQueueNameConstant generate queue name equal to queueName.
   338  func GenerateQueueNameConstant(queueName string) QueueNameGenerator {
   339  	return func(topic string) string {
   340  		return queueName
   341  	}
   342  }
   343  
   344  // GenerateQueueNameTopicNameWithSuffix generates queue name equal to:
   345  //
   346  //	topic + "_" + suffix
   347  func GenerateQueueNameTopicNameWithSuffix(suffix string) QueueNameGenerator {
   348  	return func(topic string) string {
   349  		return topic + "_" + suffix
   350  	}
   351  }
   352  
   353  type QueueConfig struct {
   354  	// GenerateRoutingKey is generated based on the topic provided for Subscribe.
   355  	GenerateName QueueNameGenerator
   356  
   357  	// Durable and Non-Auto-Deleted queues will survive server restarts and remain
   358  	// when there are no remaining consumers or bindings.  Persistent publishings will
   359  	// be restored in this queue on server restart.  These queues are only able to be
   360  	// bound to durable exchanges.
   361  	Durable bool
   362  
   363  	// Non-Durable and Auto-Deleted exchanges will be deleted when there are no
   364  	// remaining bindings and not restored on server restart.  This lifetime is
   365  	// useful for temporary topologies that should not pollute the virtual host on
   366  	// failure or after the consumers have completed.
   367  	//
   368  	// Non-Durable and Non-Auto-deleted exchanges will remain as long as the server is
   369  	// running including when there are no remaining bindings.  This is useful for
   370  	// temporary topologies that may have long delays between bindings.
   371  	AutoDelete bool
   372  
   373  	// Exclusive queues are only accessible by the connection that declares them and
   374  	// will be deleted when the connection closes.  Channels on other connections
   375  	// will receive an error when attempting  to declare, bind, consume, purge or
   376  	// delete a queue with the same name.
   377  	Exclusive bool
   378  
   379  	// When noWait is true, the queue will assume to be declared on the server.  A
   380  	// channel exception will arrive if the conditions are met for existing queues
   381  	// or attempting to modify an existing queue from a different connection.
   382  	NoWait bool
   383  
   384  	// Optional amqpe.Table of arguments that are specific to the server's implementation of
   385  	// the queue can be sent for queue types that require extra parameters.
   386  	Arguments amqp.Table
   387  }
   388  
   389  // QueueBind binds an exchange to a queue so that publishings to the exchange will
   390  // be routed to the queue when the publishing routing key matches the binding
   391  // routing key.
   392  type QueueBindConfig struct {
   393  	GenerateRoutingKey func(topic string) string
   394  
   395  	// When noWait is false and the queue could not be bound, the channel will be
   396  	// closed with an error.
   397  	NoWait bool
   398  
   399  	// Optional amqpe.Table of arguments that are specific to the server's implementation of
   400  	// the queue bind can be sent for queue bind types that require extra parameters.
   401  	Arguments amqp.Table
   402  }
   403  
   404  type PublishConfig struct {
   405  	// GenerateRoutingKey is generated based on the topic provided for Publish.
   406  	GenerateRoutingKey func(topic string) string
   407  
   408  	// Publishings can be undeliverable when the mandatory flag is true and no queue is
   409  	// bound that matches the routing key, or when the immediate flag is true and no
   410  	// consumer on the matched queue is ready to accept the delivery.
   411  	Mandatory bool
   412  
   413  	// Publishings can be undeliverable when the mandatory flag is true and no queue is
   414  	// bound that matches the routing key, or when the immediate flag is true and no
   415  	// consumer on the matched queue is ready to accept the delivery.
   416  	Immediate bool
   417  
   418  	// With transactional enabled, all messages wil be added in transaction.
   419  	Transactional bool
   420  
   421  	// ChannelPoolSize specifies the size of a channel pool. All channels in the pool are opened when the publisher is
   422  	// created. When a Publish operation is performed then a channel is taken from the pool to perform the operation and
   423  	// then returned to the pool once the operation has finished. If all channels are in use then the Publish operation
   424  	// waits until a channel is returned to the pool.
   425  	// If this value is set to 0 (default) then channels are not pooled and a new channel is opened/closed for every
   426  	// Publish operation.
   427  	ChannelPoolSize int
   428  
   429  	// ConfirmDelivery indicates whether the Publish function should wait until a confirmation is received from
   430  	// the AMQP server in order to guarantee that the message is delivered. Setting this value to true may
   431  	// negatively impact performance but will increase reliability.
   432  	ConfirmDelivery bool
   433  }
   434  
   435  type ConsumeConfig struct {
   436  	// When true, message will be not requeued when nacked.
   437  	NoRequeueOnNack bool
   438  
   439  	// The consumer is identified by a string that is unique and scoped for all
   440  	// consumers on this channel.  If you wish to eventually cancel the consumer, use
   441  	// the same non-empty identifier in Channel.Cancel.  An empty string will cause
   442  	// the library to generate a unique identity.  The consumer identity will be
   443  	// included in every Delivery in the ConsumerTag field
   444  	Consumer string
   445  
   446  	// When exclusive is true, the server will ensure that this is the sole consumer
   447  	// from this queue. When exclusive is false, the server will fairly distribute
   448  	// deliveries across multiple consumers.
   449  	Exclusive bool
   450  
   451  	// The noLocal flag is not supported by RabbitMQ.
   452  	NoLocal bool
   453  
   454  	// When noWait is true, do not wait for the server to confirm the request and
   455  	// immediately begin deliveries.  If it is not possible to consume, a channel
   456  	// exception will be raised and the channel will be closed.
   457  	NoWait bool
   458  
   459  	Qos QosConfig
   460  
   461  	// Optional arguments can be provided that have specific semantics for the queue
   462  	// or server.
   463  	Arguments amqp.Table
   464  }
   465  
   466  // Qos controls how many messages or how many bytes the server will try to keep on
   467  // the network for consumers before receiving delivery acks.  The intent of Qos is
   468  // to make sure the network buffers stay full between the server and client.
   469  type QosConfig struct {
   470  	// With a prefetch count greater than zero, the server will deliver that many
   471  	// messages to consumers before acknowledgments are received.  The server ignores
   472  	// this option when consumers are started with noAck because no acknowledgments
   473  	// are expected or sent.
   474  	//
   475  	// In order to defeat that we can set the prefetch count with the value of 1.
   476  	// This tells RabbitMQ not to give more than one message to a worker at a time.
   477  	// Or, in other words, don't dispatch a new message to a worker until it has
   478  	// processed and acknowledged the previous one.
   479  	// Instead, it will dispatch it to the next worker that is not still busy.
   480  	PrefetchCount int
   481  
   482  	// With a prefetch size greater than zero, the server will try to keep at least
   483  	// that many bytes of deliveries flushed to the network before receiving
   484  	// acknowledgments from the consumers.  This option is ignored when consumers are
   485  	// started with noAck.
   486  	PrefetchSize int
   487  
   488  	// When global is true, these Qos settings apply to all existing and future
   489  	// consumers on all channels on the same connection.  When false, the Channel.Qos
   490  	// settings will apply to all existing and future consumers on this channel.
   491  	//
   492  	// Please see the RabbitMQ Consumer Prefetch documentation for an explanation of
   493  	// how the global flag is implemented in RabbitMQ, as it differs from the
   494  	// AMQP 0.9.1 specification in that global Qos settings are limited in scope to
   495  	// channels, not connections (https://www.rabbitmq.com/consumer-prefetch.html).
   496  	Global bool
   497  }
   498  
   499  type ReconnectConfig struct {
   500  	BackoffInitialInterval     time.Duration
   501  	BackoffRandomizationFactor float64
   502  	BackoffMultiplier          float64
   503  	BackoffMaxInterval         time.Duration
   504  }
   505  
   506  func DefaultReconnectConfig() *ReconnectConfig {
   507  	return &ReconnectConfig{
   508  		BackoffInitialInterval:     500 * time.Millisecond,
   509  		BackoffRandomizationFactor: 0.5,
   510  		BackoffMultiplier:          1.5,
   511  		BackoffMaxInterval:         60 * time.Second,
   512  	}
   513  }
   514  
   515  func (r ReconnectConfig) backoffConfig() *backoff.ExponentialBackOff {
   516  	return &backoff.ExponentialBackOff{
   517  		InitialInterval:     r.BackoffInitialInterval,
   518  		RandomizationFactor: r.BackoffRandomizationFactor,
   519  		Multiplier:          r.BackoffMultiplier,
   520  		MaxInterval:         r.BackoffMaxInterval,
   521  		MaxElapsedTime:      0, // no support for disabling reconnect, only close of Pub/Sub can stop reconnecting
   522  		Clock:               backoff.SystemClock,
   523  	}
   524  }