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 }