github.com/tickoalcantara12/micro/v3@v3.0.0-20221007104245-9d75b9bcbab9/service/events/store/store.go (about) 1 // Licensed under the Apache License, Version 2.0 (the "License"); 2 // you may not use this file except in compliance with the License. 3 // You may obtain a copy of the License at 4 // 5 // https://www.apache.org/licenses/LICENSE-2.0 6 // 7 // Unless required by applicable law or agreed to in writing, software 8 // distributed under the License is distributed on an "AS IS" BASIS, 9 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 10 // See the License for the specific language governing permissions and 11 // limitations under the License. 12 // 13 // Original source: github.com/micro/go-micro/v3/events/store/store.go 14 15 package store 16 17 import ( 18 "encoding/json" 19 "time" 20 21 "github.com/tickoalcantara12/micro/v3/service/events" 22 "github.com/tickoalcantara12/micro/v3/service/logger" 23 "github.com/tickoalcantara12/micro/v3/service/store" 24 "github.com/tickoalcantara12/micro/v3/service/store/memory" 25 "github.com/pkg/errors" 26 ) 27 28 const joinKey = "/" 29 30 // NewStore returns an initialized events store 31 func NewStore(opts ...Option) events.Store { 32 // parse the options 33 var options Options 34 for _, o := range opts { 35 o(&options) 36 } 37 if options.TTL.Seconds() == 0 { 38 options.TTL = time.Hour * 24 39 } 40 if options.Store == nil { 41 options.Store = memory.NewStore() 42 } 43 44 // return the store 45 evs := &evStore{options} 46 if options.Backup != nil { 47 go evs.backupLoop() 48 } 49 return evs 50 } 51 52 type evStore struct { 53 opts Options 54 } 55 56 // Read events for a topic 57 func (s *evStore) Read(topic string, opts ...events.ReadOption) ([]*events.Event, error) { 58 // validate the topic 59 if len(topic) == 0 { 60 return nil, events.ErrMissingTopic 61 } 62 63 // parse the options 64 options := events.ReadOptions{ 65 Offset: 0, 66 Limit: 250, 67 } 68 for _, o := range opts { 69 o(&options) 70 } 71 72 // execute the request 73 recs, err := s.opts.Store.Read(topic+joinKey, 74 store.ReadPrefix(), 75 store.ReadLimit(options.Limit), 76 store.ReadOffset(options.Offset), 77 ) 78 if err != nil { 79 return nil, errors.Wrap(err, "Error reading from store") 80 } 81 82 // unmarshal the result 83 result := make([]*events.Event, len(recs)) 84 for i, r := range recs { 85 var e events.Event 86 if err := json.Unmarshal(r.Value, &e); err != nil { 87 return nil, errors.Wrap(err, "Invalid event returned from stroe") 88 } 89 result[i] = &e 90 } 91 92 return result, nil 93 } 94 95 // Write an event to the store 96 func (s *evStore) Write(event *events.Event, opts ...events.WriteOption) error { 97 // parse the options 98 options := events.WriteOptions{ 99 TTL: s.opts.TTL, 100 } 101 for _, o := range opts { 102 o(&options) 103 } 104 105 // construct the store record 106 bytes, err := json.Marshal(event) 107 if err != nil { 108 return errors.Wrap(err, "Error mashaling event to JSON") 109 } 110 // suffix event ID with hour resolution for easy retrieval in batches 111 timeSuffix := time.Now().Format("2006010215") 112 113 record := &store.Record{ 114 // key is such that reading by prefix indexes by topic and reading by suffix indexes by time 115 Key: event.Topic + joinKey + event.ID + joinKey + timeSuffix, 116 Value: bytes, 117 Expiry: options.TTL, 118 } 119 120 // write the record to the store 121 if err := s.opts.Store.Write(record); err != nil { 122 return errors.Wrap(err, "Error writing to the store") 123 } 124 125 return nil 126 } 127 128 func (s *evStore) backupLoop() { 129 for { 130 err := s.opts.Backup.Snapshot(s.opts.Store) 131 if err != nil { 132 logger.Errorf("Error running backup %s", err) 133 } 134 135 time.Sleep(1 * time.Hour) 136 } 137 } 138 139 // Backup is an interface for snapshotting the events store to long term storage 140 type Backup interface { 141 Snapshot(st store.Store) error 142 }