github.com/wfusion/gofusion@v1.1.14/mq/types.go (about)

     1  package mq
     2  
     3  import (
     4  	"context"
     5  	"reflect"
     6  
     7  	"github.com/Rican7/retry/strategy"
     8  
     9  	"github.com/wfusion/gofusion/common/infra/watermill"
    10  	"github.com/wfusion/gofusion/common/utils"
    11  	"github.com/wfusion/gofusion/log"
    12  
    13  	mw "github.com/wfusion/gofusion/common/infra/watermill/message"
    14  )
    15  
    16  const (
    17  	ErrDuplicatedSubscriberName utils.Error = "duplicated mq subscriber name"
    18  	ErrDuplicatedPublisherName  utils.Error = "duplicated mq publisher name"
    19  	ErrDuplicatedRouterName     utils.Error = "duplicated mq router name"
    20  	ErrEventHandlerConflict     utils.Error = "conflict with event handler and message handler"
    21  	ErrNotImplement             utils.Error = "mq not implement"
    22  )
    23  
    24  var (
    25  	handlerFuncType                   = reflect.TypeOf((*HandlerFunc)(nil)).Elem()
    26  	watermillHandlerFuncType          = reflect.TypeOf((*mw.HandlerFunc)(nil)).Elem()
    27  	watermillNoPublishHandlerFuncType = reflect.TypeOf((*mw.NoPublishHandlerFunc)(nil)).Elem()
    28  	watermillHandlerMiddlewareType    = reflect.TypeOf((*mw.HandlerMiddleware)(nil)).Elem()
    29  	customLoggerType                  = reflect.TypeOf((*customLogger)(nil)).Elem()
    30  	watermillLoggerType               = reflect.TypeOf((*watermill.LoggerAdapter)(nil)).Elem()
    31  
    32  	newFn = map[mqType]func(context.Context, string, string, *Conf, watermill.LoggerAdapter) (Publisher, Subscriber){
    33  		mqTypeGoChannel: newGoChannel,
    34  		mqTypeAMQP:      newAMQP,
    35  		mqTypeRabbitmq:  newAMQP,
    36  		mqTypeKafka:     newKafka,
    37  		mqTypePulsar:    newPulsar,
    38  		mqTypeRedis:     newRedis,
    39  		mqTypeMysql:     newMysql,
    40  		mqTypePostgres:  newPostgres,
    41  	}
    42  
    43  	singleConsumerMQType = utils.NewSet(mqTypeGoChannel, mqTypeMysql, mqTypePostgres)
    44  )
    45  
    46  type Publisher interface {
    47  	// Publish publishes provided messages to given topic.
    48  	//
    49  	// Publish can be synchronous or asynchronous - it depends on the implementation.
    50  	//
    51  	// Most publishers implementations don't support atomic publishing of messages.
    52  	// This means that if publishing one of the messages fails, the next messages will not be published.
    53  	//
    54  	// Publish must be thread safe.
    55  	Publish(ctx context.Context, opts ...utils.OptionExtender) error
    56  
    57  	// PublishRaw publishes provided raw messages to given topic.
    58  	//
    59  	// PublishRaw can be synchronous or asynchronous - it depends on the implementation.
    60  	//
    61  	// Most publishers implementations don't support atomic publishing of messages.
    62  	// This means that if publishing one of the messages fails, the next messages will not be published.
    63  	//
    64  	// PublishRaw must be thread safe.
    65  	PublishRaw(ctx context.Context, opts ...utils.OptionExtender) error
    66  
    67  	// close should flush unsent messages, if publisher is async.
    68  	close() error
    69  	topic() string
    70  	watermillPublisher() mw.Publisher
    71  }
    72  type EventPublisher[T eventual] interface {
    73  	// PublishEvent publishes provided messages to given topic.
    74  	//
    75  	// PublishEvent can be synchronous or asynchronous - it depends on the implementation.
    76  	//
    77  	// Most publishers implementations don't support atomic publishing of messages.
    78  	// This means that if publishing one of the messages fails, the next messages will not be published.
    79  	//
    80  	// PublishEvent must be thread safe.
    81  	PublishEvent(ctx context.Context, opts ...utils.OptionExtender) error
    82  }
    83  
    84  type IRouter interface {
    85  	Handle(handlerName string, hdr any, opts ...utils.OptionExtender)
    86  	Serve() error
    87  	Start()
    88  	Running() <-chan struct{}
    89  	close() error
    90  }
    91  
    92  type Subscriber interface {
    93  	// Subscribe returns output channel with messages from provided topic.
    94  	// Channel is closed, when Close() was called on the subscriber.
    95  	//
    96  	// When provided ctx is cancelled, subscriber will close subscribe and close output channel.
    97  	// Provided ctx is set to all produced messages.
    98  	Subscribe(ctx context.Context, opts ...utils.OptionExtender) (<-chan Message, error)
    99  
   100  	// SubscribeRaw returns output channel with original messages from provided topic.
   101  	// Channel is closed, when Close() was called on the subscriber.
   102  	//
   103  	// When provided ctx is cancelled, subscriber will close subscribe and close output channel.
   104  	// Provided ctx is set to all produced messages.
   105  	SubscribeRaw(ctx context.Context, opts ...utils.OptionExtender) (<-chan Message, error)
   106  
   107  	// close closes all subscriptions with their output channels and flush offsets etc. when needed.
   108  	close() error
   109  	topic() string
   110  	watermillLogger() watermill.LoggerAdapter
   111  	watermillSubscriber() mw.Subscriber
   112  }
   113  
   114  type EventSubscriber[T eventual] interface {
   115  	// SubscribeEvent returns output channel with events from provided topic.
   116  	// Channel is closed, when Close() was called on the subscriber.
   117  	//
   118  	// When provided ctx is cancelled, subscriber will close subscribe and close output channel.
   119  	// Provided ctx is set to all produced messages.
   120  	SubscribeEvent(ctx context.Context, opts ...utils.OptionExtender) (<-chan Event[T], error)
   121  }
   122  
   123  type HandlerFunc func(msg Message) error
   124  
   125  type Message interface {
   126  	ID() string
   127  	Payload() []byte
   128  	RawMessage() any
   129  	Context() context.Context
   130  	Object() any
   131  	Ack() bool
   132  	Nack() bool
   133  }
   134  
   135  type pubOption struct {
   136  	messages          []Message
   137  	watermillMessages mw.Messages
   138  
   139  	async           bool
   140  	asyncStrategies []strategy.Strategy
   141  
   142  	objects           []any
   143  	objectUUIDGenFunc reflect.Value
   144  }
   145  type eventPubOption[T eventual] struct {
   146  	events []Event[T]
   147  }
   148  
   149  func Objects[T any](objectUUIDGenFunc func(T) string, objects ...any) utils.OptionFunc[pubOption] {
   150  	return func(o *pubOption) {
   151  		o.objects = objects
   152  		if objectUUIDGenFunc != nil {
   153  			o.objectUUIDGenFunc = reflect.ValueOf(objectUUIDGenFunc)
   154  		}
   155  	}
   156  }
   157  func Messages(messages ...Message) utils.OptionFunc[pubOption] {
   158  	return func(o *pubOption) { o.messages = messages }
   159  }
   160  func messages(messages ...*mw.Message) utils.OptionFunc[pubOption] {
   161  	return func(o *pubOption) {
   162  		o.watermillMessages = messages
   163  	}
   164  }
   165  func Async(strategies ...strategy.Strategy) utils.OptionFunc[pubOption] {
   166  	return func(o *pubOption) {
   167  		o.async = true
   168  		o.asyncStrategies = strategies
   169  	}
   170  }
   171  func Events[T eventual](events ...Event[T]) utils.OptionFunc[eventPubOption[T]] {
   172  	return func(o *eventPubOption[T]) {
   173  		o.events = events
   174  	}
   175  }
   176  
   177  type subOption struct {
   178  	channelLength int
   179  }
   180  
   181  func ChannelLen(channelLength int) utils.OptionFunc[subOption] {
   182  	return func(o *subOption) {
   183  		o.channelLength = channelLength
   184  	}
   185  }
   186  
   187  type routerOption struct {
   188  	isEventSubscriber bool
   189  }
   190  
   191  func handleEventSubscriber() utils.OptionFunc[routerOption] {
   192  	return func(o *routerOption) {
   193  		o.isEventSubscriber = true
   194  	}
   195  }
   196  
   197  type message struct {
   198  	*mw.Message
   199  
   200  	payload []byte
   201  	obj     any
   202  }
   203  
   204  func NewMessage(uuid string, payload []byte) Message {
   205  	return &message{Message: mw.NewMessage(uuid, payload), payload: payload}
   206  }
   207  func (m *message) ID() string      { return m.Message.UUID }
   208  func (m *message) Payload() []byte { return m.payload }
   209  func (m *message) RawMessage() any { return m.Message }
   210  func (m *message) Object() any     { return m.obj }
   211  
   212  // Conf mq config
   213  //nolint: revive // struct tag too long issue
   214  type Conf struct {
   215  	Topic               string        `yaml:"topic" json:"topic" toml:"topic"`
   216  	Type                mqType        `yaml:"type" json:"type" toml:"type"`
   217  	Producer            bool          `yaml:"producer" json:"producer" toml:"producer" default:"true"`
   218  	Consumer            bool          `yaml:"consumer" json:"consumer" toml:"consumer"`
   219  	ConsumerGroup       string        `yaml:"consumer_group" json:"consumer_group" toml:"consumer_group"`
   220  	ConsumerConcurrency int           `yaml:"consumer_concurrency" json:"consumer_concurrency" toml:"consumer_concurrency"`
   221  	Endpoint            *endpointConf `yaml:"endpoint" json:"endpoint" toml:"endpoint"`
   222  	Persistent          bool          `yaml:"persistent" json:"persistent" toml:"persistent"`
   223  	SerializeType       string        `yaml:"serialize_type" json:"serialize_type" toml:"serialize_type"`
   224  	CompressType        string        `yaml:"compress_type" json:"compress_type" toml:"compress_type"`
   225  
   226  	EnableLogger bool   `yaml:"enable_logger" json:"enable_logger" toml:"enable_logger" default:"false"`
   227  	Logger       string `yaml:"logger" json:"logger" toml:"logger" default:"github.com/wfusion/gofusion/log/customlogger.mqLogger"`
   228  	LogInstance  string `yaml:"log_instance" json:"log_instance" toml:"log_instance" default:"default"`
   229  
   230  	// mongo, mysql, mariadb option
   231  	MessageScheme  string `yaml:"message_scheme" json:"message_scheme" toml:"message_scheme" default:"watermill_message"`
   232  	SeriesScheme   string `yaml:"series_scheme" json:"series_scheme" toml:"series_scheme" default:"watermill_series"`
   233  	ConsumerScheme string `yaml:"consumer_scheme" json:"consumer_scheme" toml:"consumer_scheme" default:"watermill_subscriber"`
   234  
   235  	ConsumeMiddlewares []*middlewareConf `yaml:"consume_middlewares" json:"consume_middlewares" toml:"consume_middlewares"`
   236  }
   237  
   238  type endpointConf struct {
   239  	Addresses    []string     `yaml:"addresses" json:"addresses" toml:"addresses"`
   240  	User         string       `yaml:"user" json:"user" toml:"user"`
   241  	Password     string       `yaml:"password" json:"password" toml:"password" encrypted:""`
   242  	AuthType     string       `yaml:"auth_type" json:"auth_type" toml:"auth_type"`
   243  	Instance     string       `yaml:"instance" json:"instance" toml:"instance"`
   244  	InstanceType instanceType `yaml:"instance_type" json:"instance_type" toml:"instance_type"`
   245  	Version      string       `yaml:"version" json:"version" toml:"version"`
   246  }
   247  
   248  // middlewareConf consume middleware config
   249  //nolint: revive // struct tag too long issue
   250  type middlewareConf struct {
   251  	Type middlewareType `yaml:"type" json:"type" toml:"type"`
   252  
   253  	// Throttle middleware
   254  	// Example duration and count: NewThrottle(10, time.Second) for 10 messages per second
   255  	ThrottleCount    int    `yaml:"throttle_count" json:"throttle_count" toml:"throttle_count"`
   256  	ThrottleDuration string `yaml:"throttle_duration" json:"throttle_duration" toml:"throttle_duration"`
   257  
   258  	// Retry middleware
   259  	// MaxRetries is maximum number of times a retry will be attempted.
   260  	RetryMaxRetries int `yaml:"retry_max_retries" json:"retry_max_retries" toml:"retry_max_retries"`
   261  	// RetryInitialInterval is the first interval between retries. Subsequent intervals will be scaled by Multiplier.
   262  	RetryInitialInterval string `yaml:"retry_initial_interval" json:"retry_initial_interval" toml:"retry_initial_interval"`
   263  	// RetryMaxInterval sets the limit for the exponential backoff of retries. The interval will not be increased beyond MaxInterval.
   264  	RetryMaxInterval string `yaml:"retry_max_interval" json:"retry_max_interval" toml:"retry_max_interval"`
   265  	// RetryMultiplier is the factor by which the waiting interval will be multiplied between retries.
   266  	RetryMultiplier float64 `yaml:"retry_multiplier" json:"retry_multiplier" toml:"retry_multiplier"`
   267  	// RetryMaxElapsedTime sets the time limit of how long retries will be attempted. Disabled if 0.
   268  	RetryMaxElapsedTime string `yaml:"retry_max_elapsed_time" json:"retry_max_elapsed_time" toml:"retry_max_elapsed_time"`
   269  	// RetryRandomizationFactor randomizes the spread of the backoff times within the interval of:
   270  	// [currentInterval * (1 - randomization_factor), currentInterval * (1 + randomization_factor)].
   271  	RetryRandomizationFactor float64 `yaml:"retry_randomization_factor" json:"retry_randomization_factor" toml:"retry_randomization_factor"`
   272  
   273  	// Poison middleware
   274  	// PoisonTopic salvages unprocessable messages and published them on a separate topic
   275  	PoisonTopic string `yaml:"poison_topic" json:"poison_topic" toml:"poison_topic"`
   276  
   277  	// Timeout middleware
   278  	Timeout string `yaml:"timeout" json:"timeout" toml:"timeout"`
   279  
   280  	// CircuitBreaker middleware
   281  	// CircuitBreakerMaxRequests is the maximum number of requests allowed to pass through
   282  	// when the CircuitBreaker is half-open.
   283  	// If CircuitBreakerMaxRequests is 0, the CircuitBreaker allows only 1 request.
   284  	CircuitBreakerMaxRequests uint `yaml:"circuit_breaker_max_requests" json:"circuit_breaker_max_requests" toml:"circuit_breaker_max_requests"`
   285  	// CircuitBreakerInterval is the cyclic period of the closed state
   286  	// for the CircuitBreaker to clear the internal Counts.
   287  	// If CircuitBreakerInterval is less than or equal to 0, the CircuitBreaker doesn't clear internal Counts during the closed state.
   288  	CircuitBreakerInterval string `yaml:"circuit_breaker_interval" json:"circuit_breaker_interval" toml:"circuit_breaker_interval"`
   289  	// CircuitBreakerTimeout is the period of the open state,
   290  	// after which the state of the CircuitBreaker becomes half-open.
   291  	// If CircuitBreakerTimeout is less than or equal to 0, the timeout value of the CircuitBreaker is set to 60 seconds.
   292  	CircuitBreakerTimeout string `yaml:"circuit_breaker_timeout" json:"circuit_breaker_timeout" toml:"circuit_breaker_timeout"`
   293  	// CircuitBreakerTripExpr ready to trip expression
   294  	// support params: requests, total_successes, total_failures, consecutive_successes, consecutive_failures
   295  	CircuitBreakerTripExpr string `yaml:"circuit_breaker_trip_expr" json:"circuit_breaker_trip_expr" toml:"circuit_breaker_trip_expr"`
   296  }
   297  
   298  type mqType string
   299  
   300  const (
   301  	mqTypeAMQP      mqType = "amqp"
   302  	mqTypeRabbitmq  mqType = "rabbitmq"
   303  	mqTypeGoChannel mqType = "gochannel"
   304  	mqTypeKafka     mqType = "kafka"
   305  	mqTypePulsar    mqType = "pulsar"
   306  	mqTypeRedis     mqType = "redis"
   307  	mqTypeRocketmq  mqType = "rocketmq"
   308  	mqTypeMysql     mqType = "mysql"
   309  	mqTypePostgres  mqType = "postgres"
   310  )
   311  
   312  type instanceType string
   313  
   314  const (
   315  	instanceTypeDB    instanceType = "db"
   316  	instanceTypeRedis instanceType = "redis"
   317  	instanceTypeMongo instanceType = "mongo"
   318  )
   319  
   320  type middlewareType string
   321  
   322  const (
   323  	middlewareTypeThrottle       middlewareType = "throttle"
   324  	middlewareTypeRetry          middlewareType = "retry"
   325  	middlewareTypeInstanceAck    middlewareType = "instance_ack"
   326  	middlewareTypePoison         middlewareType = "poison"
   327  	middlewareTypeTimeout        middlewareType = "timeout"
   328  	middlewareTypeCircuitBreaker middlewareType = "circuit_breaker"
   329  )
   330  
   331  type customLogger interface {
   332  	Init(log log.Loggable, appName, name string)
   333  }