github.com/wfusion/gofusion@v1.1.14/mq/event.go (about) 1 package mq 2 3 import ( 4 "context" 5 "fmt" 6 "reflect" 7 "strings" 8 "time" 9 10 "github.com/wfusion/gofusion/common/constant" 11 "github.com/wfusion/gofusion/common/infra/watermill" 12 "github.com/wfusion/gofusion/common/utils" 13 "github.com/wfusion/gofusion/common/utils/inspect" 14 15 mw "github.com/wfusion/gofusion/common/infra/watermill/message" 16 ) 17 18 const ( 19 keyEntityID = "entity_id" 20 keyEventType = "event_type" 21 eventHandlerSignature = "github.com/wfusion/gofusion/mq.eventHandler[" 22 eventHandlerWithMsgSignature = "github.com/wfusion/gofusion/mq.eventHandlerWithMsg[" 23 mqPackageSignFormat = "github.com/wfusion/gofusion/mq.%s" 24 ) 25 26 func isEventHandler(f any) bool { 27 sig := formatEventHandlerSignature(f) 28 return strings.HasPrefix(sig, eventHandlerSignature) || strings.HasPrefix(sig, eventHandlerWithMsgSignature) 29 } 30 func formatEventHandlerSignature(f any) string { 31 ft, ok := f.(reflect.Type) 32 if !ok { 33 fv, ok := f.(reflect.Value) 34 if ok { 35 ft = fv.Type() 36 } else { 37 ft = reflect.TypeOf(f) 38 } 39 } 40 41 return ft.PkgPath() + "." + ft.Name() 42 } 43 44 type eventHandler[T eventual] func(ctx context.Context, event Event[T]) error 45 type eventHandlerWithMsg[T eventual] func(ctx context.Context, event Event[T]) ([]Message, error) 46 47 func EventHandler[T eventual](hdr eventHandler[T]) eventHandler[T] { 48 return func(ctx context.Context, event Event[T]) error { 49 // TODO: dedup & discard expired event 50 return hdr(ctx, event) 51 } 52 } 53 54 func EventHandlerWithMsg[T eventual](hdr eventHandlerWithMsg[T]) eventHandlerWithMsg[T] { 55 return func(ctx context.Context, event Event[T]) ([]Message, error) { 56 // TODO: dedup & discard expired event 57 return hdr(ctx, event) 58 } 59 } 60 61 func NewEventPublisherDI[T eventual](name string, opts ...utils.OptionExtender) func() EventPublisher[T] { 62 return func() EventPublisher[T] { 63 return NewEventPublisher[T](name, opts...) 64 } 65 } 66 67 func NewEventPublisher[T eventual](name string, opts ...utils.OptionExtender) EventPublisher[T] { 68 opt := utils.ApplyOptions[useOption](opts...) 69 publisher := Pub(name, AppName(opt.appName)) 70 abstractMq := inspect.GetField[*abstractMQ](publisher, "abstractMQ") 71 return &eventPublisher[T]{abstractMQ: abstractMq} 72 } 73 74 type eventPublisher[T eventual] struct { 75 *abstractMQ 76 } 77 78 func (e *eventPublisher[T]) PublishEvent(ctx context.Context, opts ...utils.OptionExtender) (err error) { 79 opt := utils.ApplyOptions[pubOption](opts...) 80 optT := utils.ApplyOptions[eventPubOption[T]](opts...) 81 msgs := make([]*mw.Message, 0, len(optT.events)) 82 for _, evt := range optT.events { 83 msg, err := e.abstractMQ.newObjectMessage(ctx, evt.(*event[T]).pd, opt) 84 if msg != nil { 85 msg.Metadata[keyEntityID] = evt.ID() 86 msg.Metadata[keyEventType] = evt.Type() 87 } 88 if err != nil { 89 return err 90 } 91 msgs = append(msgs, msg) 92 } 93 return e.abstractMQ.Publish(ctx, messages(msgs...)) 94 } 95 96 type eventSubscriber[T eventual] struct { 97 *abstractMQ 98 evtType string 99 } 100 101 func NewEventSubscriberDI[T eventual](name string, opts ...utils.OptionExtender) func() EventSubscriber[T] { 102 return func() EventSubscriber[T] { 103 return NewEventSubscriber[T](name, opts...) 104 } 105 } 106 107 func NewEventSubscriber[T eventual](name string, opts ...utils.OptionExtender) EventSubscriber[T] { 108 opt := utils.ApplyOptions[useOption](opts...) 109 subscriber := Sub(name, AppName(opt.appName)) 110 abstractMq := inspect.GetField[*abstractMQ](subscriber, "abstractMQ") 111 112 var m reflect.Value 113 for tv := reflect.ValueOf(new(T)); tv.Kind() == reflect.Ptr; tv = tv.Elem() { 114 if m = tv.MethodByName("EventType"); m.IsValid() { 115 break 116 } 117 } 118 eventType := m.Call(nil)[0].String() 119 return &eventSubscriber[T]{abstractMQ: abstractMq, evtType: eventType} 120 } 121 122 func (e *eventSubscriber[T]) SubscribeEvent(ctx context.Context, opts ...utils.OptionExtender) ( 123 dst <-chan Event[T], err error) { 124 opt := utils.ApplyOptions[subOption](opts...) 125 out := make(chan Event[T], opt.channelLength) 126 r := Use(e.name, AppName(e.appName)).(*router) 127 r.Handle( 128 e.evtType, 129 EventHandlerWithMsg[T](func(ctx context.Context, event Event[T]) (msgs []Message, err error) { 130 select { 131 case out <- event: 132 case <-r.Router.ClosingInProgressCh: 133 event.Nack() 134 e.logger.Info(fmt.Sprintf("event subscriber %s exited", e.name), nil) 135 return 136 case <-ctx.Done(): 137 event.Nack() 138 e.logger.Info(fmt.Sprintf( 139 "event subscriber %s exited with a message nacked when business ctx done", e.name), 140 watermill.LogFields{watermill.ContextLogFieldKey: ctx}) 141 return 142 case <-e.ctx.Done(): 143 event.Nack() 144 e.logger.Info(fmt.Sprintf( 145 "event subscriber %s exited with a message nacked when app ctx done", e.name), 146 watermill.LogFields{watermill.ContextLogFieldKey: ctx}) 147 return 148 } 149 150 msgs = append(msgs, 151 &message{Message: &mw.Message{Metadata: mw.Metadata{watermill.MessageRouterAck: ""}}}) 152 return 153 }), 154 handleEventSubscriber(), 155 ) 156 return out, err 157 } 158 159 type eventual interface{ EventType() string } 160 type Event[T eventual] interface { 161 ID() string 162 Type() string 163 CreatedAt() time.Time 164 UpdatedAt() time.Time 165 DeletedAt() time.Time 166 Payload() T 167 Context() context.Context 168 Ack() bool 169 Nack() bool 170 } 171 172 func NewEvent[T eventual](id string, createdAt, updatedAt, deletedAt time.Time, payload T) Event[T] { 173 return newEvent[T](id, createdAt, updatedAt, deletedAt, payload) 174 } 175 func UntimedEvent[T eventual](id string, payload T) Event[T] { 176 return newEvent[T](id, time.Time{}, time.Time{}, time.Time{}, payload) 177 } 178 func EventCreated[T eventual](id string, createdAt time.Time, payload T) Event[T] { 179 return newEvent[T](id, createdAt, time.Time{}, time.Time{}, payload) 180 } 181 func EventUpdated[T eventual](id string, updatedAt time.Time, payload T) Event[T] { 182 return newEvent[T](id, time.Time{}, updatedAt, time.Time{}, payload) 183 } 184 func EventDeleted[T eventual](id string, deletedAt time.Time, payload T) Event[T] { 185 return newEvent[T](id, time.Time{}, time.Time{}, deletedAt, payload) 186 } 187 188 type eventPayload[T eventual] struct { 189 I string `json:"i,omitempty"` 190 T string `json:"t,omitempty"` 191 P T `json:"p,omitempty"` 192 C string `json:"c,omitempty"` 193 CL string `json:"cl,omitempty"` 194 U string `json:"u,omitempty"` 195 UL string `json:"ul,omitempty"` 196 D string `json:"d,omitempty"` 197 DL string `json:"dl,omitempty"` 198 N int64 `json:"n,omitempty"` 199 } 200 type event[T eventual] struct { 201 ctx context.Context 202 ackfn func() bool 203 nackfn func() bool 204 pd *eventPayload[T] 205 } 206 207 func newEvent[T eventual](id string, createdAt, updatedAt, deletedAt time.Time, payload T) Event[T] { 208 return &event[T]{ 209 pd: &eventPayload[T]{ 210 I: id, 211 T: payload.EventType(), 212 P: payload, 213 C: createdAt.Format(time.RFC3339Nano), 214 CL: createdAt.Location().String(), 215 U: updatedAt.Format(time.RFC3339Nano), 216 UL: updatedAt.Location().String(), 217 D: deletedAt.Format(time.RFC3339Nano), 218 DL: deletedAt.Location().String(), 219 N: time.Now().UnixNano(), 220 }, 221 } 222 } 223 func (e *event[T]) ID() string { return e.pd.I } 224 func (e *event[T]) Type() string { return e.pd.T } 225 func (e *event[T]) Payload() T { return e.pd.P } 226 func (e *event[T]) CreatedAt() time.Time { return e.toTime(e.pd.C, e.pd.CL) } 227 func (e *event[T]) UpdatedAt() time.Time { return e.toTime(e.pd.U, e.pd.UL) } 228 func (e *event[T]) DeletedAt() time.Time { return e.toTime(e.pd.D, e.pd.DL) } 229 func (e *event[T]) Context() context.Context { return e.ctx } 230 func (e *event[T]) Ack() bool { 231 if e.ackfn != nil { 232 return e.ackfn() 233 } 234 return true 235 } 236 func (e *event[T]) Nack() bool { 237 if e.nackfn != nil { 238 return e.nackfn() 239 } 240 return true 241 } 242 243 func (e *event[T]) toTime(timestr, locationstr string) (t time.Time) { 244 loc, err := time.LoadLocation(locationstr) 245 if err != nil { 246 loc = constant.DefaultLocation() 247 } 248 t, _ = time.ParseInLocation(time.RFC3339Nano, timestr, loc) 249 return 250 }