github.com/tickoalcantara12/micro/v3@v3.0.0-20221007104245-9d75b9bcbab9/service/events/client/stream.go (about) 1 package client 2 3 import ( 4 gocontext "context" 5 "encoding/json" 6 "time" 7 8 pb "github.com/tickoalcantara12/micro/v3/proto/events" 9 "github.com/tickoalcantara12/micro/v3/service/client" 10 "github.com/tickoalcantara12/micro/v3/service/context" 11 "github.com/tickoalcantara12/micro/v3/service/events" 12 "github.com/tickoalcantara12/micro/v3/service/events/util" 13 log "github.com/tickoalcantara12/micro/v3/service/logger" 14 ) 15 16 // NewStream returns an initialized stream service 17 func NewStream() events.Stream { 18 return new(stream) 19 } 20 21 type stream struct { 22 Client pb.StreamService 23 } 24 25 func (s *stream) Publish(topic string, msg interface{}, opts ...events.PublishOption) error { 26 // parse the options 27 options := events.PublishOptions{ 28 Timestamp: time.Now(), 29 } 30 for _, o := range opts { 31 o(&options) 32 } 33 34 // encode the message if it's not already encoded 35 var payload []byte 36 if p, ok := msg.([]byte); ok { 37 payload = p 38 } else { 39 p, err := json.Marshal(msg) 40 if err != nil { 41 return events.ErrEncodingMessage 42 } 43 payload = p 44 } 45 46 // execute the RPC 47 _, err := s.client().Publish(context.DefaultContext, &pb.PublishRequest{ 48 Topic: topic, 49 Payload: payload, 50 Metadata: options.Metadata, 51 Timestamp: options.Timestamp.Unix(), 52 }, client.WithAuthToken()) 53 54 return err 55 } 56 57 func (s *stream) Consume(topic string, opts ...events.ConsumeOption) (<-chan events.Event, error) { 58 // parse options 59 options := events.ConsumeOptions{ 60 AutoAck: true, 61 Context: gocontext.TODO(), 62 } 63 64 for _, o := range opts { 65 o(&options) 66 } 67 68 subReq := &pb.ConsumeRequest{ 69 Topic: topic, 70 Group: options.Group, 71 Offset: options.Offset.Unix(), 72 AutoAck: options.AutoAck, 73 AckWait: options.AckWait.Nanoseconds(), 74 RetryLimit: int64(options.GetRetryLimit()), 75 } 76 77 // start the stream 78 // TODO: potentially pass in the context defined by the user 79 stream, err := s.client().Consume(context.DefaultContext, subReq, client.WithAuthToken()) 80 if err != nil { 81 return nil, err 82 } 83 84 evChan := make(chan events.Event) 85 86 go func() { 87 for { 88 89 ev, err := stream.Recv() 90 if err != nil { 91 log.Errorf("Error receiving from stream %s", err) 92 close(evChan) 93 stream.Close() 94 return 95 } 96 97 evt := util.DeserializeEvent(ev) 98 if !options.AutoAck { 99 evt.SetNackFunc(func() error { 100 return stream.SendMsg(&pb.AckRequest{Id: evt.ID, Success: false}) 101 }) 102 evt.SetAckFunc(func() error { 103 return stream.SendMsg(&pb.AckRequest{Id: evt.ID, Success: true}) 104 }) 105 } 106 107 select { 108 case evChan <- evt: 109 case <-options.Context.Done(): 110 log.Info("Consuming stream context canceled") 111 close(evChan) 112 stream.Close() 113 return 114 } 115 } 116 }() 117 118 return evChan, nil 119 } 120 121 // this is a tmp solution since the client isn't initialized when NewStream is called. There is a 122 // fix in the works in another PR. 123 func (s *stream) client() pb.StreamService { 124 if s.Client == nil { 125 s.Client = pb.NewStreamService("events", client.DefaultClient) 126 } 127 return s.Client 128 }