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  }