gitee.com/sasukebo/go-micro/v4@v4.7.1/events/store.go (about) 1 package events 2 3 import ( 4 "encoding/json" 5 "time" 6 7 "gitee.com/sasukebo/go-micro/v4/logger" 8 "gitee.com/sasukebo/go-micro/v4/store" 9 "github.com/pkg/errors" 10 ) 11 12 const joinKey = "/" 13 14 // NewStore returns an initialized events store 15 func NewStore(opts ...StoreOption) Store { 16 // parse the options 17 var options StoreOptions 18 for _, o := range opts { 19 o(&options) 20 } 21 if options.TTL.Seconds() == 0 { 22 options.TTL = time.Hour * 24 23 } 24 25 // return the store 26 evs := &evStore{ 27 opts: options, 28 store: store.NewMemoryStore(), 29 } 30 if options.Backup != nil { 31 go evs.backupLoop() 32 } 33 return evs 34 } 35 36 type evStore struct { 37 opts StoreOptions 38 store store.Store 39 } 40 41 // Read events for a topic 42 func (s *evStore) Read(topic string, opts ...ReadOption) ([]*Event, error) { 43 // validate the topic 44 if len(topic) == 0 { 45 return nil, ErrMissingTopic 46 } 47 48 // parse the options 49 options := ReadOptions{ 50 Offset: 0, 51 Limit: 250, 52 } 53 for _, o := range opts { 54 o(&options) 55 } 56 57 // execute the request 58 recs, err := s.store.Read(topic+joinKey, 59 store.ReadPrefix(), 60 store.ReadLimit(options.Limit), 61 store.ReadOffset(options.Offset), 62 ) 63 if err != nil { 64 return nil, errors.Wrap(err, "Error reading from store") 65 } 66 67 // unmarshal the result 68 result := make([]*Event, len(recs)) 69 for i, r := range recs { 70 var e Event 71 if err := json.Unmarshal(r.Value, &e); err != nil { 72 return nil, errors.Wrap(err, "Invalid event returned from stroe") 73 } 74 result[i] = &e 75 } 76 77 return result, nil 78 } 79 80 // Write an event to the store 81 func (s *evStore) Write(event *Event, opts ...WriteOption) error { 82 // parse the options 83 options := WriteOptions{ 84 TTL: s.opts.TTL, 85 } 86 for _, o := range opts { 87 o(&options) 88 } 89 90 // construct the store record 91 bytes, err := json.Marshal(event) 92 if err != nil { 93 return errors.Wrap(err, "Error mashaling event to JSON") 94 } 95 // suffix event ID with hour resolution for easy retrieval in batches 96 timeSuffix := time.Now().Format("2006010215") 97 98 record := &store.Record{ 99 // key is such that reading by prefix indexes by topic and reading by suffix indexes by time 100 Key: event.Topic + joinKey + event.ID + joinKey + timeSuffix, 101 Value: bytes, 102 Expiry: options.TTL, 103 } 104 105 // write the record to the store 106 if err := s.store.Write(record); err != nil { 107 return errors.Wrap(err, "Error writing to the store") 108 } 109 110 return nil 111 } 112 113 func (s *evStore) backupLoop() { 114 for { 115 err := s.opts.Backup.Snapshot(s.store) 116 if err != nil { 117 logger.Errorf("Error running backup %s", err) 118 } 119 120 time.Sleep(1 * time.Hour) 121 } 122 } 123 124 // Backup is an interface for snapshotting the events store to long term storage 125 type Backup interface { 126 Snapshot(st store.Store) error 127 }