go-micro.dev/v5@v5.12.0/store/nats-js-kv/nats.go (about)

     1  // Package natsjskv is a go-micro store plugin for NATS JetStream Key-Value store.
     2  package natsjskv
     3  
     4  import (
     5  	"context"
     6  	"encoding/json"
     7  	"sync"
     8  	"time"
     9  
    10  	"github.com/cornelk/hashmap"
    11  	"github.com/nats-io/nats.go"
    12  	"github.com/pkg/errors"
    13  	"go-micro.dev/v5/store"
    14  )
    15  
    16  var (
    17  	// ErrBucketNotFound is returned when the requested bucket does not exist.
    18  	ErrBucketNotFound = errors.New("Bucket (database) not found")
    19  )
    20  
    21  // KeyValueEnvelope is the data structure stored in the key value store.
    22  type KeyValueEnvelope struct {
    23  	Key      string                 `json:"key"`
    24  	Data     []byte                 `json:"data"`
    25  	Metadata map[string]interface{} `json:"metadata"`
    26  }
    27  
    28  type natsStore struct {
    29  	sync.Once
    30  	sync.RWMutex
    31  
    32  	encoding    string
    33  	ttl         time.Duration
    34  	storageType nats.StorageType
    35  	description string
    36  
    37  	opts      store.Options
    38  	nopts     nats.Options
    39  	jsopts    []nats.JSOpt
    40  	kvConfigs []*nats.KeyValueConfig
    41  
    42  	conn    *nats.Conn
    43  	js      nats.JetStreamContext
    44  	buckets *hashmap.Map[string, nats.KeyValue]
    45  }
    46  
    47  // NewStore will create a new NATS JetStream Object Store.
    48  func NewStore(opts ...store.Option) store.Store {
    49  	options := store.Options{
    50  		Nodes:    []string{},
    51  		Database: "default",
    52  		Table:    "",
    53  		Context:  context.Background(),
    54  	}
    55  
    56  	n := &natsStore{
    57  		description: "KeyValue storage administered by go-micro store plugin",
    58  		opts:        options,
    59  		jsopts:      []nats.JSOpt{},
    60  		kvConfigs:   []*nats.KeyValueConfig{},
    61  		buckets:     hashmap.New[string, nats.KeyValue](),
    62  		storageType: nats.FileStorage,
    63  	}
    64  
    65  	n.setOption(opts...)
    66  
    67  	return n
    68  }
    69  
    70  // Init initializes the store. It must perform any required setup on the
    71  // backing storage implementation and check that it is ready for use,
    72  // returning any errors.
    73  func (n *natsStore) Init(opts ...store.Option) error {
    74  	n.setOption(opts...)
    75  
    76  	// Connect to NATS servers
    77  	conn, err := n.nopts.Connect()
    78  	if err != nil {
    79  		return errors.Wrap(err, "Failed to connect to NATS Server")
    80  	}
    81  
    82  	// Create JetStream context
    83  	js, err := conn.JetStream(n.jsopts...)
    84  	if err != nil {
    85  		return errors.Wrap(err, "Failed to create JetStream context")
    86  	}
    87  
    88  	n.conn = conn
    89  	n.js = js
    90  
    91  	// Create default config if no configs present
    92  	if len(n.kvConfigs) == 0 {
    93  		if _, err := n.mustGetBucketByName(n.opts.Database); err != nil {
    94  			return err
    95  		}
    96  	}
    97  
    98  	// Create kv store buckets
    99  	for _, cfg := range n.kvConfigs {
   100  		if _, err := n.mustGetBucket(cfg); err != nil {
   101  			return err
   102  		}
   103  	}
   104  
   105  	return nil
   106  }
   107  
   108  func (n *natsStore) setOption(opts ...store.Option) {
   109  	for _, o := range opts {
   110  		o(&n.opts)
   111  	}
   112  
   113  	n.Once.Do(func() {
   114  		n.nopts = nats.GetDefaultOptions()
   115  	})
   116  
   117  	// Extract options from context
   118  	if nopts, ok := n.opts.Context.Value(natsOptionsKey{}).(nats.Options); ok {
   119  		n.nopts = nopts
   120  	}
   121  
   122  	if jsopts, ok := n.opts.Context.Value(jsOptionsKey{}).([]nats.JSOpt); ok {
   123  		n.jsopts = append(n.jsopts, jsopts...)
   124  	}
   125  
   126  	if cfg, ok := n.opts.Context.Value(kvOptionsKey{}).([]*nats.KeyValueConfig); ok {
   127  		n.kvConfigs = append(n.kvConfigs, cfg...)
   128  	}
   129  
   130  	if ttl, ok := n.opts.Context.Value(ttlOptionsKey{}).(time.Duration); ok {
   131  		n.ttl = ttl
   132  	}
   133  
   134  	if sType, ok := n.opts.Context.Value(memoryOptionsKey{}).(nats.StorageType); ok {
   135  		n.storageType = sType
   136  	}
   137  
   138  	if text, ok := n.opts.Context.Value(descriptionOptionsKey{}).(string); ok {
   139  		n.description = text
   140  	}
   141  
   142  	if encoding, ok := n.opts.Context.Value(keyEncodeOptionsKey{}).(string); ok {
   143  		n.encoding = encoding
   144  	}
   145  
   146  	// Assign store option server addresses to nats options
   147  	if len(n.opts.Nodes) > 0 {
   148  		n.nopts.Url = ""
   149  		n.nopts.Servers = n.opts.Nodes
   150  	}
   151  
   152  	if len(n.nopts.Servers) == 0 && n.nopts.Url == "" {
   153  		n.nopts.Url = nats.DefaultURL
   154  	}
   155  }
   156  
   157  // Options allows you to view the current options.
   158  func (n *natsStore) Options() store.Options {
   159  	return n.opts
   160  }
   161  
   162  // Read takes a single key name and optional ReadOptions. It returns matching []*Record or an error.
   163  func (n *natsStore) Read(key string, opts ...store.ReadOption) ([]*store.Record, error) {
   164  	if err := n.initConn(); err != nil {
   165  		return nil, err
   166  	}
   167  
   168  	opt := store.ReadOptions{}
   169  
   170  	for _, o := range opts {
   171  		o(&opt)
   172  	}
   173  
   174  	if opt.Database == "" {
   175  		opt.Database = n.opts.Database
   176  	}
   177  
   178  	if opt.Table == "" {
   179  		opt.Table = n.opts.Table
   180  	}
   181  
   182  	bucket, ok := n.buckets.Get(opt.Database)
   183  	if !ok {
   184  		return nil, ErrBucketNotFound
   185  	}
   186  
   187  	keys, err := n.natsKeys(bucket, opt.Table, key, opt.Prefix, opt.Suffix)
   188  	if err != nil {
   189  		return nil, err
   190  	}
   191  
   192  	records := make([]*store.Record, 0, len(keys))
   193  
   194  	for _, key := range keys {
   195  		rec, ok, err := n.getRecord(bucket, key)
   196  		if err != nil {
   197  			return nil, err
   198  		}
   199  
   200  		if ok {
   201  			records = append(records, rec)
   202  		}
   203  	}
   204  
   205  	return enforceLimits(records, opt.Limit, opt.Offset), nil
   206  }
   207  
   208  // Write writes a record to the store, and returns an error if the record was not written.
   209  func (n *natsStore) Write(rec *store.Record, opts ...store.WriteOption) error {
   210  	if err := n.initConn(); err != nil {
   211  		return err
   212  	}
   213  
   214  	opt := store.WriteOptions{}
   215  	for _, o := range opts {
   216  		o(&opt)
   217  	}
   218  
   219  	if opt.Database == "" {
   220  		opt.Database = n.opts.Database
   221  	}
   222  
   223  	if opt.Table == "" {
   224  		opt.Table = n.opts.Table
   225  	}
   226  
   227  	store, err := n.mustGetBucketByName(opt.Database)
   228  	if err != nil {
   229  		return err
   230  	}
   231  
   232  	b, err := json.Marshal(KeyValueEnvelope{
   233  		Key:      rec.Key,
   234  		Data:     rec.Value,
   235  		Metadata: rec.Metadata,
   236  	})
   237  	if err != nil {
   238  		return errors.Wrap(err, "Failed to marshal object")
   239  	}
   240  
   241  	if _, err := store.Put(n.NatsKey(opt.Table, rec.Key), b); err != nil {
   242  		return errors.Wrapf(err, "Failed to store data in bucket '%s'", n.NatsKey(opt.Table, rec.Key))
   243  	}
   244  
   245  	return nil
   246  }
   247  
   248  // Delete removes the record with the corresponding key from the store.
   249  func (n *natsStore) Delete(key string, opts ...store.DeleteOption) error {
   250  	if err := n.initConn(); err != nil {
   251  		return err
   252  	}
   253  
   254  	opt := store.DeleteOptions{}
   255  
   256  	for _, o := range opts {
   257  		o(&opt)
   258  	}
   259  
   260  	if opt.Database == "" {
   261  		opt.Database = n.opts.Database
   262  	}
   263  
   264  	if opt.Table == "" {
   265  		opt.Table = n.opts.Table
   266  	}
   267  
   268  	if opt.Table == "DELETE_BUCKET" {
   269  		n.buckets.Del(key)
   270  
   271  		if err := n.js.DeleteKeyValue(key); err != nil {
   272  			return errors.Wrap(err, "Failed to delete bucket")
   273  		}
   274  
   275  		return nil
   276  	}
   277  
   278  	store, ok := n.buckets.Get(opt.Database)
   279  	if !ok {
   280  		return ErrBucketNotFound
   281  	}
   282  
   283  	if err := store.Delete(n.NatsKey(opt.Table, key)); err != nil {
   284  		return errors.Wrap(err, "Failed to delete data")
   285  	}
   286  
   287  	return nil
   288  }
   289  
   290  // List returns any keys that match, or an empty list with no error if none matched.
   291  func (n *natsStore) List(opts ...store.ListOption) ([]string, error) {
   292  	if err := n.initConn(); err != nil {
   293  		return nil, err
   294  	}
   295  
   296  	opt := store.ListOptions{}
   297  	for _, o := range opts {
   298  		o(&opt)
   299  	}
   300  
   301  	if opt.Database == "" {
   302  		opt.Database = n.opts.Database
   303  	}
   304  
   305  	if opt.Table == "" {
   306  		opt.Table = n.opts.Table
   307  	}
   308  
   309  	store, ok := n.buckets.Get(opt.Database)
   310  	if !ok {
   311  		return nil, ErrBucketNotFound
   312  	}
   313  
   314  	keys, err := n.microKeys(store, opt.Table, opt.Prefix, opt.Suffix)
   315  	if err != nil {
   316  		return nil, errors.Wrap(err, "Failed to list keys in bucket")
   317  	}
   318  
   319  	return enforceLimits(keys, opt.Limit, opt.Offset), nil
   320  }
   321  
   322  // Close the store.
   323  func (n *natsStore) Close() error {
   324  	n.conn.Close()
   325  	return nil
   326  }
   327  
   328  // String returns the name of the implementation.
   329  func (n *natsStore) String() string {
   330  	return "NATS JetStream KeyValueStore"
   331  }
   332  
   333  // thread safe way to initialize the connection.
   334  func (n *natsStore) initConn() error {
   335  	if n.hasConn() {
   336  		return nil
   337  	}
   338  
   339  	n.Lock()
   340  	defer n.Unlock()
   341  
   342  	// check if conn was initialized meanwhile
   343  	if n.conn != nil {
   344  		return nil
   345  	}
   346  
   347  	return n.Init()
   348  }
   349  
   350  // thread safe way to check if n is initialized.
   351  func (n *natsStore) hasConn() bool {
   352  	n.RLock()
   353  	defer n.RUnlock()
   354  
   355  	return n.conn != nil
   356  }
   357  
   358  // mustGetDefaultBucket returns the bucket with the given name creating it with default configuration if needed.
   359  func (n *natsStore) mustGetBucketByName(name string) (nats.KeyValue, error) {
   360  	return n.mustGetBucket(&nats.KeyValueConfig{
   361  		Bucket:      name,
   362  		Description: n.description,
   363  		TTL:         n.ttl,
   364  		Storage:     n.storageType,
   365  	})
   366  }
   367  
   368  // mustGetBucket creates a new bucket if it does not exist yet.
   369  func (n *natsStore) mustGetBucket(kv *nats.KeyValueConfig) (nats.KeyValue, error) {
   370  	if store, ok := n.buckets.Get(kv.Bucket); ok {
   371  		return store, nil
   372  	}
   373  
   374  	store, err := n.js.KeyValue(kv.Bucket)
   375  	if err != nil {
   376  		if !errors.Is(err, nats.ErrBucketNotFound) {
   377  			return nil, errors.Wrapf(err, "Failed to get bucket (%s)", kv.Bucket)
   378  		}
   379  
   380  		store, err = n.js.CreateKeyValue(kv)
   381  		if err != nil {
   382  			return nil, errors.Wrapf(err, "Failed to create bucket (%s)", kv.Bucket)
   383  		}
   384  	}
   385  
   386  	n.buckets.Set(kv.Bucket, store)
   387  
   388  	return store, nil
   389  }
   390  
   391  // getRecord returns the record with the given key from the nats kv store.
   392  func (n *natsStore) getRecord(bucket nats.KeyValue, key string) (*store.Record, bool, error) {
   393  	obj, err := bucket.Get(key)
   394  	if errors.Is(err, nats.ErrKeyNotFound) {
   395  		return nil, false, store.ErrNotFound
   396  	} else if err != nil {
   397  		return nil, false, errors.Wrap(err, "Failed to get object from bucket")
   398  	}
   399  
   400  	var kv KeyValueEnvelope
   401  	if err := json.Unmarshal(obj.Value(), &kv); err != nil {
   402  		return nil, false, errors.Wrap(err, "Failed to unmarshal object")
   403  	}
   404  
   405  	if obj.Operation() != nats.KeyValuePut {
   406  		return nil, false, nil
   407  	}
   408  
   409  	return &store.Record{
   410  		Key:      kv.Key,
   411  		Value:    kv.Data,
   412  		Metadata: kv.Metadata,
   413  	}, true, nil
   414  }
   415  
   416  func (n *natsStore) natsKeys(bucket nats.KeyValue, table, key string, prefix, suffix bool) ([]string, error) {
   417  	if !suffix && !prefix {
   418  		return []string{n.NatsKey(table, key)}, nil
   419  	}
   420  
   421  	toS := func(s string, b bool) string {
   422  		if b {
   423  			return s
   424  		}
   425  
   426  		return ""
   427  	}
   428  
   429  	keys, _, err := n.getKeys(bucket, table, toS(key, prefix), toS(key, suffix))
   430  
   431  	return keys, err
   432  }
   433  
   434  func (n *natsStore) microKeys(bucket nats.KeyValue, table, prefix, suffix string) ([]string, error) {
   435  	_, keys, err := n.getKeys(bucket, table, prefix, suffix)
   436  
   437  	return keys, err
   438  }
   439  
   440  func (n *natsStore) getKeys(bucket nats.KeyValue, table string, prefix, suffix string) ([]string, []string, error) {
   441  	names, err := bucket.Keys(nats.IgnoreDeletes())
   442  	if errors.Is(err, nats.ErrKeyNotFound) {
   443  		return []string{}, []string{}, nil
   444  	} else if err != nil {
   445  		return []string{}, []string{}, errors.Wrap(err, "Failed to list objects")
   446  	}
   447  
   448  	natsKeys := make([]string, 0, len(names))
   449  	microKeys := make([]string, 0, len(names))
   450  
   451  	for _, k := range names {
   452  		mkey, ok := n.MicroKeyFilter(table, k, prefix, suffix)
   453  		if !ok {
   454  			continue
   455  		}
   456  
   457  		natsKeys = append(natsKeys, k)
   458  		microKeys = append(microKeys, mkey)
   459  	}
   460  
   461  	return natsKeys, microKeys, nil
   462  }
   463  
   464  // enforces offset and limit without causing a panic.
   465  func enforceLimits[V any](recs []V, limit, offset uint) []V {
   466  	l := uint(len(recs))
   467  
   468  	from := offset
   469  	if from > l {
   470  		from = l
   471  	}
   472  
   473  	to := l
   474  	if limit > 0 && offset+limit < l {
   475  		to = offset + limit
   476  	}
   477  
   478  	return recs[from:to]
   479  }