github.com/angryronald/go-kit@v0.0.0-20240505173814-ff2bd9c79dbf/generic/repository/memcached/generic.repository.go (about) 1 package memcached 2 3 import ( 4 "context" 5 "fmt" 6 "time" 7 8 "github.com/google/uuid" 9 "github.com/redis/go-redis/v9" 10 11 "github.com/angryronald/go-kit/cast" 12 "github.com/angryronald/go-kit/generic/repository" 13 ) 14 15 const defaultIDPropertyName string = "ID" 16 17 type KeyIdentifier struct { 18 key string 19 isCollection bool 20 propertyNameMapToKey string 21 } 22 23 func NewKeyIdentifier( 24 key string, 25 isCollection bool, 26 propertyNameMapToKey string, 27 ) *KeyIdentifier { 28 return &KeyIdentifier{ 29 key: key, 30 isCollection: isCollection, 31 propertyNameMapToKey: propertyNameMapToKey, 32 } 33 } 34 35 type GenericRepository struct { 36 client *redis.Client 37 key string 38 expirationDuration time.Duration 39 keyIdentifiers []*KeyIdentifier 40 } 41 42 func (r *GenericRepository) isKeyIdentifierRegistered(key string) bool { 43 for _, keyIdentifier := range r.keyIdentifiers { 44 if keyIdentifier.key == key { 45 return true 46 } 47 } 48 return false 49 } 50 51 func (r *GenericRepository) getKeyIdentifier(key string) *KeyIdentifier { 52 for _, keyIdentifier := range r.keyIdentifiers { 53 if keyIdentifier.key == key { 54 return keyIdentifier 55 } 56 } 57 return nil 58 } 59 60 func (r *GenericRepository) findByKeyIdentifier(ctx context.Context, keyIdentifierWanted string, key string, model interface{}) (interface{}, error) { 61 for _, keyIdentifier := range r.keyIdentifiers { 62 if keyIdentifier.key == keyIdentifierWanted { 63 var result interface{} 64 if keyIdentifier.isCollection { 65 result = CreateStructPointerSlice(model, 0) 66 } else { 67 result = CreateNewStructObject(model) 68 } 69 70 resultRaw, err := r.client.Get(ctx, fmt.Sprintf("%s%s%s", keyIdentifier.key, CONJUNCTION, key)).Result() 71 if err != nil { 72 return nil, err 73 } 74 75 cast.FromBytes([]byte(resultRaw), &result) 76 77 if !keyIdentifier.isCollection { 78 return cast.ObjectToStructPointerSlice(result), nil 79 } 80 81 return result, nil 82 } 83 } 84 return nil, repository.ErrNotFound 85 } 86 87 // isParametersValid return 88 // 1. true if either all parameters are key identifiers or all parameters are filtered parameters 89 // 2. true if all parameters are key identifiers 90 func (r *GenericRepository) isParametersValidAndAllParametersAreKeyIdentifiers(params map[string]interface{}) (bool, bool) { 91 numberOfKeyIdentifierParams := 0 92 93 for key, _ := range params { 94 if r.isKeyIdentifierRegistered(key) { 95 numberOfKeyIdentifierParams++ 96 } 97 } 98 99 return (numberOfKeyIdentifierParams == len(params)) || (numberOfKeyIdentifierParams == 0), (numberOfKeyIdentifierParams == len(params)) 100 } 101 102 func (r *GenericRepository) insertInAllKeys(ctx context.Context, data interface{}) error { 103 for _, keyIdentifier := range r.keyIdentifiers { 104 key, err := repository.GetStructPropertyAsString(data, keyIdentifier.propertyNameMapToKey) 105 if err != nil { 106 return err 107 } 108 109 if keyIdentifier.isCollection { 110 dataInArray := CreateStructPointerSlice(data, 0) 111 112 resultRaw, err := r.client.Get(ctx, fmt.Sprintf("%s%s%s", keyIdentifier.key, CONJUNCTION, key)).Result() 113 if err != nil && err != redis.Nil { 114 return err 115 } 116 117 cast.FromBytes([]byte(resultRaw), &dataInArray) 118 119 resultInArray := cast.StructPointerArrayToInterfacePointerArray(dataInArray) 120 resultInArray = append(resultInArray, data) 121 122 dataBytes, err := cast.ToBytes(resultInArray) 123 if err != nil { 124 return err 125 } 126 127 if err = r.client.Set( 128 ctx, 129 fmt.Sprintf("%s%s%s", keyIdentifier.key, CONJUNCTION, key), 130 dataBytes, 131 r.expirationDuration).Err(); err != nil { 132 return err 133 } 134 } else { 135 dataBytes, err := cast.ToBytes(data) 136 if err != nil { 137 return err 138 } 139 140 if err = r.client.Set( 141 ctx, 142 fmt.Sprintf("%s%s%s", keyIdentifier.key, CONJUNCTION, key), 143 dataBytes, 144 r.expirationDuration).Err(); err != nil { 145 return err 146 } 147 } 148 } 149 150 return nil 151 } 152 153 func (r *GenericRepository) upsertInAllKeys(ctx context.Context, data interface{}) error { 154 for _, keyIdentifier := range r.keyIdentifiers { 155 key, err := repository.GetStructPropertyAsString(data, keyIdentifier.propertyNameMapToKey) 156 if err != nil { 157 return err 158 } 159 160 if keyIdentifier.isCollection { 161 dataInArray := CreateStructPointerSlice(data, 0) 162 163 resultRaw, err := r.client.Get(ctx, fmt.Sprintf("%s%s%s", keyIdentifier.key, CONJUNCTION, key)).Result() 164 if err != nil && err != redis.Nil { 165 return err 166 } 167 168 cast.FromBytes([]byte(resultRaw), &dataInArray) 169 170 // ignore error if not found 171 resultInArray, err := UpdateCollection(dataInArray, data) 172 if err == repository.ErrNotFound { 173 resultInArray = append(resultInArray, data) 174 } 175 176 dataBytes, err := cast.ToBytes(resultInArray) 177 if err != nil { 178 return err 179 } 180 181 if err = r.client.Set( 182 ctx, 183 fmt.Sprintf("%s%s%s", keyIdentifier.key, CONJUNCTION, key), 184 dataBytes, 185 r.expirationDuration).Err(); err != nil { 186 return err 187 } 188 } else { 189 dataBytes, err := cast.ToBytes(data) 190 if err != nil { 191 return err 192 } 193 194 if err = r.client.Set( 195 ctx, 196 fmt.Sprintf("%s%s%s", keyIdentifier.key, CONJUNCTION, key), 197 dataBytes, 198 r.expirationDuration).Err(); err != nil { 199 return err 200 } 201 } 202 } 203 204 return nil 205 } 206 207 func (r *GenericRepository) deleteInAllKeys(ctx context.Context, data interface{}) error { 208 for _, keyIdentifier := range r.keyIdentifiers { 209 key, err := repository.GetStructPropertyAsString(data, keyIdentifier.propertyNameMapToKey) 210 if err != nil { 211 return err 212 } 213 214 if keyIdentifier.isCollection { 215 dataInArray := CreateStructPointerSlice(data, 0) 216 217 resultRaw, err := r.client.Get(ctx, fmt.Sprintf("%s%s%s", keyIdentifier.key, CONJUNCTION, key)).Result() 218 if err != nil { 219 return err 220 } 221 222 cast.FromBytes([]byte(resultRaw), &dataInArray) 223 224 // ignore error if not found 225 resultInArray, err := DeleteCollection(dataInArray, data) 226 if err == repository.ErrNotFound { 227 resultInArray = append(resultInArray, data) 228 } 229 230 dataBytes, err := cast.ToBytes(resultInArray) 231 if err != nil { 232 return err 233 } 234 235 if err = r.client.Set( 236 ctx, 237 fmt.Sprintf("%s%s%s", keyIdentifier.key, CONJUNCTION, key), 238 dataBytes, 239 r.expirationDuration).Err(); err != nil { 240 return err 241 } 242 } else { 243 if err = r.client.Del(ctx, fmt.Sprintf("%s%s%s", keyIdentifier.key, CONJUNCTION, key)).Err(); err != nil { 244 return err 245 } 246 } 247 } 248 249 return nil 250 } 251 252 func (r *GenericRepository) bulkInsertInAllKeys(ctx context.Context, data map[string]map[string][]interface{}) error { 253 for keyIdentifier, dataInKeyMap := range data { 254 registeredKeyIdentifier := r.getKeyIdentifier(keyIdentifier) 255 if registeredKeyIdentifier.isCollection { 256 for key, dataInArray := range dataInKeyMap { 257 dataBytes, err := cast.ToBytes(dataInArray) 258 if err != nil { 259 return err 260 } 261 262 if err = r.client.Set( 263 ctx, 264 fmt.Sprintf("%s%s%s", keyIdentifier, CONJUNCTION, key), 265 dataBytes, 266 r.expirationDuration).Err(); err != nil { 267 return err 268 } 269 } 270 } else { 271 for key, dataInArray := range dataInKeyMap { 272 dataBytes, err := cast.ToBytes(dataInArray[0]) 273 if err != nil { 274 return err 275 } 276 277 if err = r.client.Set( 278 ctx, 279 fmt.Sprintf("%s%s%s", keyIdentifier, CONJUNCTION, key), 280 dataBytes, 281 r.expirationDuration).Err(); err != nil { 282 return err 283 } 284 } 285 } 286 } 287 return nil 288 } 289 290 func (r *GenericRepository) bulkUpsertInAllKeys(ctx context.Context, data map[string]map[string][]interface{}) error { 291 for keyIdentifier, dataInKeyMap := range data { 292 registeredKeyIdentifier := r.getKeyIdentifier(keyIdentifier) 293 if registeredKeyIdentifier.isCollection { 294 for key, dataInArray := range dataInKeyMap { 295 dataBytes, err := cast.ToBytes(dataInArray) 296 if err != nil { 297 return err 298 } 299 300 if err = r.client.Del(ctx, fmt.Sprintf("%s%s%s", keyIdentifier, CONJUNCTION, key)).Err(); err != nil { 301 return err 302 } 303 304 if err = r.client.Set( 305 ctx, 306 fmt.Sprintf("%s%s%s", keyIdentifier, CONJUNCTION, key), 307 dataBytes, 308 r.expirationDuration).Err(); err != nil { 309 return err 310 } 311 } 312 } else { 313 for key, dataInArray := range dataInKeyMap { 314 dataBytes, err := cast.ToBytes(dataInArray[0]) 315 if err != nil { 316 return err 317 } 318 319 if err = r.client.Del(ctx, fmt.Sprintf("%s%s%s", keyIdentifier, CONJUNCTION, key)).Err(); err != nil { 320 return err 321 } 322 323 if err = r.client.Set( 324 ctx, 325 fmt.Sprintf("%s%s%s", keyIdentifier, CONJUNCTION, key), 326 dataBytes, 327 r.expirationDuration).Err(); err != nil { 328 return err 329 } 330 } 331 } 332 } 333 return nil 334 } 335 336 func (r *GenericRepository) transformDataToKeyMap(data interface{}) map[string]map[string][]interface{} { 337 dataMapToKey := map[string]map[string][]interface{}{} 338 for _, keyIdentifier := range r.keyIdentifiers { 339 dataMapToKey[keyIdentifier.key] = map[string][]interface{}{} 340 } 341 342 dataInArray := cast.StructPointerArrayToInterfacePointerArray(data) 343 for _, dt := range dataInArray { 344 for _, keyIdentifier := range r.keyIdentifiers { 345 key, err := repository.GetStructPropertyAsString(dt, keyIdentifier.propertyNameMapToKey) 346 if err != nil { 347 return nil 348 } 349 if dataMapToKey[keyIdentifier.key][key] == nil { 350 dataMapToKey[keyIdentifier.key][key] = []interface{}{} 351 } 352 dataMapToKey[keyIdentifier.key][key] = append(dataMapToKey[keyIdentifier.key][key], dt) 353 } 354 } 355 356 return dataMapToKey 357 } 358 359 // FindAll to get all the data with filtered by parameters given (Not Recommended passing the params on memcached, the process might slowed down and take more memory since it is relying on reflect) 360 func (r *GenericRepository) FindAll(ctx context.Context, params map[string]interface{}, conditionalOperations []repository.ConditionalOperation, relationalOperations []repository.RelationalOperation, page int, limit int, result interface{}) (interface{}, error) { 361 var isConditionFulfill bool 362 var err error 363 364 if !repository.IsValidOperations(conditionalOperations, relationalOperations, params) { 365 return nil, repository.ErrBadParameters 366 } 367 368 resultRaw, err := r.client.Get(ctx, r.key).Result() 369 if err != nil { 370 if err == redis.Nil { 371 return nil, repository.ErrNotFound 372 } 373 return nil, err 374 } 375 376 resultInArray := CreateStructPointerSlice(result, 0) 377 cast.FromBytes([]byte(resultRaw), &resultInArray) 378 379 filteredResult := cast.StructPointerArrayToInterfacePointerArray(resultInArray) 380 381 var finalResult []interface{} 382 if len(params) > 0 { 383 isParametersValid, allParametersAreKeyIdentifiers := r.isParametersValidAndAllParametersAreKeyIdentifiers(params) 384 if !isParametersValid { 385 return nil, repository.ErrOperationIsNotAllowed 386 } 387 388 if allParametersAreKeyIdentifiers { 389 for key, value := range params { 390 valueInString, err := cast.ToString(value) 391 if err != nil { 392 return nil, err 393 } 394 395 singleResultRaw, err := r.findByKeyIdentifier(ctx, key, valueInString, result) 396 if err != nil { 397 if err == redis.Nil { 398 return nil, repository.ErrNotFound 399 } 400 return nil, err 401 } 402 403 singleResult := cast.StructPointerArrayToInterfacePointerArray(singleResultRaw) 404 finalResult = append(finalResult, singleResult...) 405 } 406 } else { 407 for _, singleResult := range filteredResult { 408 if isConditionFulfill, err = extractObjectAndCheckConditions(singleResult, params, conditionalOperations, relationalOperations); err != nil { 409 return nil, err 410 } 411 412 if isConditionFulfill { 413 finalResult = append(finalResult, singleResult) 414 } 415 } 416 } 417 } else { 418 finalResult = filteredResult 419 } 420 421 if page == 0 && limit == 0 { 422 return finalResult, nil 423 } 424 425 if page == 0 { 426 page = 1 427 } 428 429 if limit == 0 { 430 limit = 10 431 } 432 433 finalResult = finalResult[(page-1)*limit:] 434 if len(finalResult) > 0 { 435 if limit > len(finalResult) { 436 limit = len(finalResult) 437 } 438 finalResult = finalResult[:limit] 439 } 440 return finalResult, nil 441 } 442 443 func (r *GenericRepository) FindOne(ctx context.Context, key string, value interface{}, result interface{}) (interface{}, error) { 444 singleResultRaw, err := r.FindAll(ctx, map[string]interface{}{key: value}, []repository.ConditionalOperation{repository.EQUAL_WITH}, nil, 1, 1, result) 445 if err != nil { 446 return nil, err 447 } 448 singleResult := cast.StructPointerArrayToInterfaceArray(singleResultRaw) 449 if len(singleResult) == 0 { 450 return nil, repository.ErrNotFound 451 } 452 return singleResult[0], nil 453 } 454 455 func (r *GenericRepository) FindByID(ctx context.Context, id uuid.UUID, result interface{}) (interface{}, error) { 456 resultRaw, err := r.client.Get(ctx, fmt.Sprintf("%s%s%s", r.key, CONJUNCTION, id)).Result() 457 if err != nil { 458 if err == redis.Nil { 459 return nil, repository.ErrNotFound 460 } 461 return nil, err 462 } 463 464 cast.FromBytes([]byte(resultRaw), &result) 465 return result, nil 466 } 467 468 func (r *GenericRepository) Insert(ctx context.Context, data interface{}) (interface{}, error) { 469 result := CreateStructPointerSlice(data, 0) 470 resultRaw, err := r.client.Get(ctx, r.key).Result() 471 if err != nil && err != redis.Nil { 472 return nil, err 473 } 474 475 cast.FromBytes([]byte(resultRaw), &result) 476 477 resultInArray := cast.StructPointerArrayToInterfacePointerArray(result) 478 resultInArray = append([]interface{}{data}, resultInArray...) 479 480 dataBytes, err := cast.ToBytes(resultInArray) 481 if err != nil { 482 return nil, err 483 } 484 485 if err = r.client.Set( 486 ctx, 487 r.key, 488 dataBytes, 489 r.expirationDuration).Err(); err != nil { 490 return nil, err 491 } 492 493 singleDataBytes, err := cast.ToBytes(data) 494 if err != nil { 495 return nil, err 496 } 497 498 id, err := repository.GetStructPropertyAsString(data, defaultIDPropertyName) 499 if err != nil { 500 return nil, err 501 } 502 503 if err = r.client.Set( 504 ctx, 505 fmt.Sprintf("%s%s%s", r.key, CONJUNCTION, id), 506 singleDataBytes, 507 r.expirationDuration).Err(); err != nil { 508 return nil, err 509 } 510 511 if err = r.insertInAllKeys(ctx, data); err != nil { 512 return nil, err 513 } 514 515 return data, nil 516 } 517 518 func (r *GenericRepository) Update(ctx context.Context, data interface{}) (interface{}, error) { 519 result := CreateStructPointerSlice(data, 0) 520 resultRaw, err := r.client.Get(ctx, r.key).Result() 521 if err != nil { 522 if err == redis.Nil { 523 return nil, repository.ErrNotFound 524 } 525 return nil, err 526 } 527 528 cast.FromBytes([]byte(resultRaw), &result) 529 530 resultInArray, err := UpdateCollection(result, data) 531 if err != nil { 532 return nil, err 533 } 534 535 dataBytes, err := cast.ToBytes(resultInArray) 536 if err != nil { 537 return nil, err 538 } 539 540 if err = r.client.Set( 541 ctx, 542 r.key, 543 dataBytes, 544 r.expirationDuration).Err(); err != nil { 545 return nil, err 546 } 547 548 singleDataBytes, err := cast.ToBytes(data) 549 if err != nil { 550 return nil, err 551 } 552 553 id, err := repository.GetStructPropertyAsString(data, defaultIDPropertyName) 554 if err != nil { 555 return nil, err 556 } 557 558 if err = r.client.Set( 559 ctx, 560 fmt.Sprintf("%s%s%s", r.key, CONJUNCTION, id), 561 singleDataBytes, 562 r.expirationDuration).Err(); err != nil { 563 return nil, err 564 } 565 566 if err = r.upsertInAllKeys(ctx, data); err != nil { 567 return nil, err 568 } 569 570 return data, nil 571 } 572 573 func (r *GenericRepository) Delete(ctx context.Context, data interface{}) (interface{}, error) { 574 result := CreateStructPointerSlice(data, 0) 575 resultRaw, err := r.client.Get(ctx, r.key).Result() 576 if err != nil { 577 if err == redis.Nil { 578 return nil, repository.ErrNotFound 579 } 580 return nil, err 581 } 582 583 cast.FromBytes([]byte(resultRaw), &result) 584 585 resultInArray, err := DeleteCollection(result, data) 586 if err != nil { 587 return nil, err 588 } 589 590 dataBytes, err := cast.ToBytes(resultInArray) 591 if err != nil { 592 return nil, err 593 } 594 595 if err = r.client.Set( 596 ctx, 597 r.key, 598 dataBytes, 599 r.expirationDuration).Err(); err != nil { 600 return nil, err 601 } 602 603 id, err := repository.GetStructPropertyAsString(data, defaultIDPropertyName) 604 if err != nil { 605 return nil, err 606 } 607 608 if err = r.client.Del(ctx, fmt.Sprintf("%s%s%s", r.key, CONJUNCTION, id)).Err(); err != nil { 609 return nil, err 610 } 611 612 if err = r.deleteInAllKeys(ctx, data); err != nil { 613 return nil, err 614 } 615 616 return data, nil 617 } 618 619 func (r *GenericRepository) Upsert(ctx context.Context, data interface{}) (interface{}, error) { 620 result := CreateStructPointerSlice(data, 0) 621 resultRaw, err := r.client.Get(ctx, r.key).Result() 622 if err != nil && err != redis.Nil { 623 return nil, err 624 } 625 626 cast.FromBytes([]byte(resultRaw), &result) 627 628 resultInArray, err := UpdateCollection(result, data) 629 if err == repository.ErrNotFound { 630 resultInArray = append(cast.StructPointerArrayToInterfacePointerArray(result), data) 631 } 632 633 dataBytes, err := cast.ToBytes(resultInArray) 634 if err != nil { 635 return nil, err 636 } 637 638 if err = r.client.Set( 639 ctx, 640 r.key, 641 dataBytes, 642 r.expirationDuration).Err(); err != nil { 643 return nil, err 644 } 645 646 singleDataBytes, err := cast.ToBytes(data) 647 if err != nil { 648 return nil, err 649 } 650 651 id, err := repository.GetStructPropertyAsString(data, defaultIDPropertyName) 652 if err != nil { 653 return nil, err 654 } 655 656 if err = r.client.Set( 657 ctx, 658 fmt.Sprintf("%s%s%s", r.key, CONJUNCTION, id), 659 singleDataBytes, 660 r.expirationDuration).Err(); err != nil { 661 return nil, err 662 } 663 664 if err = r.upsertInAllKeys(ctx, data); err != nil { 665 return nil, err 666 } 667 668 return data, nil 669 } 670 671 // BulkInsert for now just going to do bulkinsert for whole data 672 /* 673 consideration - trade offs: 674 + much faster 675 - needs to implement sharding services for reading and writing to cutoff the data size as soon as possible 676 */ 677 func (r *GenericRepository) BulkInsert(ctx context.Context, data interface{}) (interface{}, error) { 678 dataBytes, err := cast.ToBytes(data) 679 if err != nil && err != redis.Nil { 680 return nil, err 681 } 682 683 if err = r.client.Set( 684 ctx, 685 r.key, 686 dataBytes, 687 r.expirationDuration).Err(); err != nil { 688 return nil, err 689 } 690 691 dataInArray := cast.StructPointerArrayToInterfacePointerArray(data) 692 for _, dt := range dataInArray { 693 singleDataBytes, err := cast.ToBytes(dt) 694 if err != nil { 695 return nil, err 696 } 697 698 id, err := repository.GetStructPropertyAsString(dt, defaultIDPropertyName) 699 if err != nil { 700 return nil, err 701 } 702 703 if err = r.client.Set( 704 ctx, 705 fmt.Sprintf("%s%s%s", r.key, CONJUNCTION, id), 706 singleDataBytes, 707 r.expirationDuration).Err(); err != nil { 708 return nil, err 709 } 710 } 711 712 if err = r.bulkInsertInAllKeys(ctx, r.transformDataToKeyMap(data)); err != nil { 713 return nil, err 714 } 715 716 return data, nil 717 } 718 719 // BulkUpsert for now just going to do bulkupsert for whole data by removing and reinsert new data 720 /* 721 consideration - trade offs: 722 + much faster 723 - needs to implement sharding services for reading and writing to cutoff the data size as soon as possible 724 */ 725 func (r *GenericRepository) BulkUpsert(ctx context.Context, data interface{}) (interface{}, error) { 726 dataBytes, err := cast.ToBytes(data) 727 if err != nil && err != redis.Nil { 728 return nil, err 729 } 730 731 if err = r.client.Del(ctx, r.key).Err(); err != nil { 732 return nil, err 733 } 734 735 if err = r.client.Set( 736 ctx, 737 r.key, 738 dataBytes, 739 r.expirationDuration).Err(); err != nil { 740 return nil, err 741 } 742 743 dataInArray := cast.StructPointerArrayToInterfacePointerArray(data) 744 for _, dt := range dataInArray { 745 singleDataBytes, err := cast.ToBytes(dt) 746 if err != nil { 747 return nil, err 748 } 749 750 id, err := repository.GetStructPropertyAsString(dt, defaultIDPropertyName) 751 if err != nil { 752 return nil, err 753 } 754 755 if err = r.client.Set( 756 ctx, 757 fmt.Sprintf("%s%s%s", r.key, CONJUNCTION, id), 758 singleDataBytes, 759 r.expirationDuration).Err(); err != nil { 760 return nil, err 761 } 762 } 763 764 if err = r.bulkUpsertInAllKeys(ctx, r.transformDataToKeyMap(data)); err != nil { 765 return nil, err 766 } 767 768 return data, nil 769 } 770 771 func (r *GenericRepository) Query(ctx context.Context, query string, params []interface{}, result interface{}) (interface{}, error) { 772 // Not Implemented on memcached 773 return nil, repository.ErrNotImplement 774 } 775 776 func NewRepository(client *redis.Client, key string, expirationDuration time.Duration, keyIdentifiers []*KeyIdentifier) repository.GenericRepositoryInterface { 777 return &GenericRepository{ 778 client: client, 779 key: key, 780 expirationDuration: expirationDuration, 781 keyIdentifiers: keyIdentifiers, 782 } 783 } 784 785 func NewMutableRepository(client *redis.Client, key string, expirationDuration time.Duration, keyIdentifiers []*KeyIdentifier) repository.MutableGenericRepositoryInterface { 786 return &GenericRepository{ 787 client: client, 788 key: key, 789 expirationDuration: expirationDuration, 790 keyIdentifiers: keyIdentifiers, 791 } 792 } 793 794 func NewImmutableRepository(client *redis.Client, key string, expirationDuration time.Duration, keyIdentifiers []*KeyIdentifier) repository.ImmutableGenericRepositoryInterface { 795 return &GenericRepository{ 796 client: client, 797 key: key, 798 expirationDuration: expirationDuration, 799 keyIdentifiers: keyIdentifiers, 800 } 801 }