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  }