github.com/wfusion/gofusion@v1.1.14/mq/mq.go (about) 1 package mq 2 3 import ( 4 "context" 5 "fmt" 6 "reflect" 7 8 "github.com/Rican7/retry" 9 10 "github.com/wfusion/gofusion/common/infra/watermill" 11 "github.com/wfusion/gofusion/common/utils" 12 "github.com/wfusion/gofusion/common/utils/clone" 13 "github.com/wfusion/gofusion/common/utils/compress" 14 "github.com/wfusion/gofusion/common/utils/serialize" 15 "github.com/wfusion/gofusion/routine" 16 17 mw "github.com/wfusion/gofusion/common/infra/watermill/message" 18 fusCtx "github.com/wfusion/gofusion/context" 19 pd "github.com/wfusion/gofusion/internal/util/payload" 20 ) 21 22 type abstractMQ struct { 23 pub mw.Publisher 24 sub mw.Subscriber 25 26 appName string 27 ctx context.Context 28 name string 29 conf *Conf 30 logger watermill.LoggerAdapter 31 32 compressType compress.Algorithm 33 serializeType serialize.Algorithm 34 } 35 36 func newPub(ctx context.Context, pub mw.Publisher, appName, name string, 37 conf *Conf, logger watermill.LoggerAdapter) *abstractMQ { 38 mq := &abstractMQ{ctx: ctx, pub: pub, appName: appName, name: name, conf: clone.Slowly(conf), logger: logger} 39 mq.serializeType = serialize.ParseAlgorithm(conf.SerializeType) 40 mq.compressType = compress.ParseAlgorithm(conf.CompressType) 41 return mq 42 } 43 44 func newSub(ctx context.Context, sub mw.Subscriber, appName, name string, 45 conf *Conf, logger watermill.LoggerAdapter) *abstractMQ { 46 mq := &abstractMQ{ctx: ctx, sub: sub, appName: appName, name: name, conf: clone.Slowly(conf), logger: logger} 47 mq.serializeType = serialize.ParseAlgorithm(conf.SerializeType) 48 mq.compressType = compress.ParseAlgorithm(conf.CompressType) 49 return mq 50 } 51 52 func (a *abstractMQ) Publish(ctx context.Context, opts ...utils.OptionExtender) (err error) { 53 opt := utils.ApplyOptions[pubOption](opts...) 54 msgs := opt.watermillMessages 55 for _, msg := range opt.messages { 56 msg, err := a.newMessage(ctx, msg, opt) 57 if err != nil { 58 return err 59 } 60 msgs = append(msgs, msg) 61 } 62 for _, object := range opt.objects { 63 msg, err := a.newObjectMessage(ctx, object, opt) 64 if err != nil { 65 return err 66 } 67 msgs = append(msgs, msg) 68 } 69 if len(msgs) == 0 { 70 logInfo(ctx, a.logger, a.appName, a.name, "none messages to publish") 71 return 72 } 73 74 if !opt.async { 75 return a.pub.Publish(ctx, a.conf.Topic, msgs...) 76 } 77 78 routine.Goc(ctx, func() { 79 idList := utils.SliceMapping(msgs, func(s *mw.Message) (id string) { return s.UUID }) 80 idList = utils.NewSet(idList...).Items() 81 retryFunc := func(attempt uint) error { 82 if attempt > 1 { 83 logInfo(ctx, a.logger, a.appName, a.name, 84 "retry to publish topic message [topic[%s] message%v[%v] attempt[%v]]", 85 a.conf.Topic, idList, len(msgs), attempt-1) 86 } 87 return a.pub.Publish(ctx, a.conf.Topic, msgs...) 88 } 89 90 if err = retry.Retry(retryFunc, opt.asyncStrategies...); err != nil { 91 logError(ctx, a.logger, a.appName, a.name, 92 "retry to publish topic message failed [err[%s] topic[%s] message%v[%v]]", 93 err, a.conf.Topic, idList, len(msgs)) 94 } 95 }, routine.AppName(a.appName)) 96 return 97 } 98 99 func (a *abstractMQ) PublishRaw(ctx context.Context, opts ...utils.OptionExtender) (err error) { 100 opt := utils.ApplyOptions[pubOption](opts...) 101 msgs := opt.watermillMessages 102 for _, msg := range opt.messages { 103 wmsg := mw.NewMessage(msg.ID(), msg.Payload()) 104 wmsg.Metadata = fusCtx.WatermillMetadata(ctx) 105 wmsg.SetContext(ctx) 106 msgs = append(msgs, wmsg) 107 } 108 if len(msgs) == 0 { 109 logInfo(ctx, a.logger, a.appName, a.name, "none messages to publish") 110 return 111 } 112 113 if !opt.async { 114 return a.pub.Publish(ctx, a.conf.Topic, msgs...) 115 } 116 117 routine.Goc(ctx, func() { 118 idList := utils.SliceMapping(msgs, func(s *mw.Message) (id string) { return s.UUID }) 119 idList = utils.NewSet(idList...).Items() 120 retryFunc := func(attempt uint) error { 121 if attempt > 1 { 122 logInfo(ctx, a.logger, a.appName, a.name, 123 "retry to publish topic message [topic[%s] message%v[%v] attempt[%v]]", 124 a.conf.Topic, idList, len(msgs), attempt-1) 125 } 126 return a.pub.Publish(ctx, a.conf.Topic, msgs...) 127 } 128 129 if err = retry.Retry(retryFunc, opt.asyncStrategies...); err != nil { 130 logError(ctx, a.logger, a.appName, a.name, 131 "retry to publish topic message failed [err[%s] topic[%s] message%v[%v]]", 132 err, a.conf.Topic, idList, len(msgs)) 133 } 134 }, routine.AppName(a.appName)) 135 return 136 } 137 138 func (a *abstractMQ) SubscribeRaw(ctx context.Context, opts ...utils.OptionExtender) (dst <-chan Message, err error) { 139 opt := utils.ApplyOptions[subOption](opts...) 140 ch, err := a.sub.Subscribe(ctx, a.conf.Topic) 141 if err != nil { 142 return 143 } 144 145 msgCh := make(chan Message, opt.channelLength) 146 routine.Go(func() { 147 defer close(msgCh) 148 for { 149 select { 150 case wmsg, ok := <-ch: 151 if !ok { 152 return 153 } 154 msg := rawMessageConvertFrom(wmsg) 155 select { 156 case msgCh <- msg: 157 case <-ctx.Done(): 158 msg.Nack() 159 a.logger.Info(fmt.Sprintf( 160 "raw subscriber %s exited with a message nacked when business ctx done", a.name), 161 watermill.LogFields{watermill.ContextLogFieldKey: ctx}) 162 return 163 case <-a.ctx.Done(): 164 msg.Nack() 165 a.logger.Info(fmt.Sprintf( 166 "raw subscriber %s exited with a message nacked when app ctx done", a.name), 167 watermill.LogFields{watermill.ContextLogFieldKey: ctx}) 168 return 169 } 170 case <-ctx.Done(): 171 return 172 case <-a.ctx.Done(): 173 return 174 } 175 } 176 }, routine.AppName(a.appName)) 177 return msgCh, err 178 } 179 180 func (a *abstractMQ) Subscribe(ctx context.Context, opts ...utils.OptionExtender) (dst <-chan Message, err error) { 181 opt := utils.ApplyOptions[subOption](opts...) 182 ch, err := a.sub.Subscribe(ctx, a.conf.Topic) 183 if err != nil { 184 return 185 } 186 187 msgCh := make(chan Message, opt.channelLength) 188 routine.Go(func() { 189 defer close(msgCh) 190 for { 191 select { 192 case wmsg, ok := <-ch: 193 if !ok { 194 return 195 } 196 _, data, isRaw, err := pd.UnsealRaw(wmsg.Payload, pd.Compress(a.compressType)) 197 if err != nil { 198 a.logger.Error("unseal message failed", err, watermill.LogFields{ 199 watermill.ContextLogFieldKey: ctx, 200 }) 201 continue 202 } 203 wmsg.SetContext(fusCtx.New(fusCtx.Watermill(wmsg.Metadata))) 204 msg := &message{Message: wmsg, payload: data} 205 if !isRaw { 206 _, msg.obj, _, err = pd.Unseal(wmsg.Payload, 207 pd.Serialize(a.serializeType), pd.Compress(a.compressType)) 208 if err != nil { 209 a.logger.Error("unseal message object failed", err, watermill.LogFields{ 210 watermill.ContextLogFieldKey: ctx, 211 }) 212 continue 213 } 214 } 215 select { 216 case msgCh <- msg: 217 case <-ctx.Done(): 218 msg.Nack() 219 a.logger.Info(fmt.Sprintf( 220 "subscriber %s exited with a message nacked when business ctx done", a.name), 221 watermill.LogFields{watermill.ContextLogFieldKey: ctx}) 222 return 223 case <-a.ctx.Done(): 224 msg.Nack() 225 a.logger.Info(fmt.Sprintf( 226 "subscriber %s exited with a message nacked when app ctx done", a.name), 227 watermill.LogFields{watermill.ContextLogFieldKey: ctx}) 228 return 229 } 230 231 case <-ctx.Done(): 232 return 233 case <-a.ctx.Done(): 234 return 235 } 236 } 237 }, routine.AppName(a.appName)) 238 return msgCh, err 239 } 240 241 func (a *abstractMQ) close() error { panic(ErrNotImplement) } 242 func (a *abstractMQ) topic() string { return a.conf.Topic } 243 func (a *abstractMQ) watermillPublisher() mw.Publisher { return a.pub } 244 func (a *abstractMQ) watermillSubscriber() mw.Subscriber { return a.sub } 245 func (a *abstractMQ) watermillLogger() watermill.LoggerAdapter { return a.logger } 246 247 func (a *abstractMQ) newMessage(ctx context.Context, src Message, _ *pubOption) ( 248 msg *mw.Message, err error) { 249 payload, err := pd.Seal(src.Payload(), pd.Compress(a.compressType)) 250 if err != nil { 251 return 252 } 253 msg = mw.NewMessage(src.ID(), payload) 254 msg.Metadata = fusCtx.WatermillMetadata(ctx) 255 msg.SetContext(ctx) 256 return 257 } 258 func (a *abstractMQ) newObjectMessage(ctx context.Context, object any, opt *pubOption) ( 259 msg *mw.Message, err error) { 260 payload, err := pd.Seal(object, pd.Compress(a.compressType), pd.Serialize(a.serializeType)) 261 if err != nil { 262 return 263 } 264 uuid := utils.ULID() 265 if opt.objectUUIDGenFunc.IsValid() && opt.objectUUIDGenFunc.Kind() == reflect.Func { 266 inType := opt.objectUUIDGenFunc.Type().In(0) 267 inParam := reflect.ValueOf(object).Convert(inType) 268 uuid = opt.objectUUIDGenFunc.Call([]reflect.Value{inParam})[0].Interface().(string) 269 } 270 msg = mw.NewMessage(uuid, payload) 271 msg.Metadata = fusCtx.WatermillMetadata(ctx) 272 msg.SetContext(ctx) 273 return 274 } 275 276 func rawMessageConvertFrom(src *mw.Message) (dst Message) { 277 return &message{Message: src, payload: src.Payload} 278 } 279 280 func messageConvertTo(src Message) (dst *mw.Message) { 281 dst = src.(*message).Message 282 return 283 } 284 285 func messageConvertFrom(src *mw.Message, 286 serializeType serialize.Algorithm, compressType compress.Algorithm) (dst Message, err error) { 287 _, data, isRaw, err := pd.UnsealRaw(src.Payload, pd.Compress(compressType)) 288 if err != nil { 289 return 290 } 291 src.SetContext(fusCtx.New(fusCtx.Watermill(src.Metadata))) 292 msg := &message{Message: src, payload: data} 293 if !isRaw { 294 _, msg.obj, _, err = pd.Unseal(src.Payload, 295 pd.Serialize(serializeType), pd.Compress(compressType)) 296 if err != nil { 297 return 298 } 299 } 300 dst = msg 301 return 302 }