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  }