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 }