robocanic commented on code in PR #1344:
URL: https://github.com/apache/dubbo-admin/pull/1344#discussion_r2484236430


##########
pkg/store/db/mysql.go:
##########
@@ -17,4 +17,499 @@
 
 package db
 
-// TODO implement memory resource store, refer to GORM https://gorm.io/docs/
+import (
+       "errors"
+       "fmt"
+       "reflect"
+       "sort"
+       "sync"
+
+       storecfg "github.com/apache/dubbo-admin/pkg/config/store"
+       "github.com/go-logr/logr"
+       "gorm.io/driver/mysql"
+       "gorm.io/gorm"
+       "k8s.io/client-go/tools/cache"
+
+       "github.com/apache/dubbo-admin/pkg/common/bizerror"
+       "github.com/apache/dubbo-admin/pkg/common/log"
+       "github.com/apache/dubbo-admin/pkg/core/resource/model"
+       "github.com/apache/dubbo-admin/pkg/core/runtime"
+       "github.com/apache/dubbo-admin/pkg/core/store"
+)
+
+func init() {
+       store.RegisterFactory(&mysqlStoreFactory{})
+}
+
+type mysqlStoreFactory struct{}
+
+var _ store.Factory = &mysqlStoreFactory{}
+
+func (f *mysqlStoreFactory) Support(s storecfg.Type) bool {
+       return s == storecfg.MySQL
+}
+
+func (f *mysqlStoreFactory) New(kind model.ResourceKind, cfg *storecfg.Config) 
(store.ManagedResourceStore, error) {
+       return NewMySQLStore(kind, cfg.Address)
+}
+
+type mysqlStore struct {
+       db          *gorm.DB
+       kind        model.ResourceKind
+       indexers    cache.Indexers
+       indexerLock sync.RWMutex
+       logger      logr.Logger
+}
+
+var _ store.ManagedResourceStore = &mysqlStore{}
+
+func NewMySQLStore(kind model.ResourceKind, address string) 
(store.ManagedResourceStore, error) {
+       db, err := gorm.Open(mysql.Open(address), &gorm.Config{})
+       if err != nil {
+               return nil, fmt.Errorf("failed to connect to mysql: %w", err)
+       }
+
+       if err := db.AutoMigrate(&ResourceModel{}); err != nil {
+               return nil, fmt.Errorf("failed to migrate schema: %w", err)
+       }
+
+       return &mysqlStore{
+               db:       db,
+               kind:     kind,
+               indexers: cache.Indexers{},
+               logger:   log.NewLogger(log.InfoLevel).WithName("mysql-store"),
+       }, nil
+}
+
+func (ms *mysqlStore) Init(_ runtime.BuilderContext) error {
+       return nil

Review Comment:
   suggestion: 可能这样做更符合dubbo-admin这个component 
的框架标准:在New()中初始化好对象属性字段,在Init()方法这里初始化连接等外部配置,在Start()中处理进行优雅下线,通过监听 stop 
cahnnel来做连接池的shutdown等优雅下线的工作;



##########
pkg/store/db/mysql.go:
##########
@@ -17,4 +17,499 @@
 
 package db
 
-// TODO implement memory resource store, refer to GORM https://gorm.io/docs/
+import (
+       "errors"
+       "fmt"
+       "reflect"
+       "sort"
+       "sync"
+
+       storecfg "github.com/apache/dubbo-admin/pkg/config/store"
+       "github.com/go-logr/logr"
+       "gorm.io/driver/mysql"
+       "gorm.io/gorm"
+       "k8s.io/client-go/tools/cache"
+
+       "github.com/apache/dubbo-admin/pkg/common/bizerror"
+       "github.com/apache/dubbo-admin/pkg/common/log"
+       "github.com/apache/dubbo-admin/pkg/core/resource/model"
+       "github.com/apache/dubbo-admin/pkg/core/runtime"
+       "github.com/apache/dubbo-admin/pkg/core/store"
+)
+
+func init() {
+       store.RegisterFactory(&mysqlStoreFactory{})
+}
+
+type mysqlStoreFactory struct{}
+
+var _ store.Factory = &mysqlStoreFactory{}
+
+func (f *mysqlStoreFactory) Support(s storecfg.Type) bool {
+       return s == storecfg.MySQL
+}
+
+func (f *mysqlStoreFactory) New(kind model.ResourceKind, cfg *storecfg.Config) 
(store.ManagedResourceStore, error) {
+       return NewMySQLStore(kind, cfg.Address)
+}
+
+type mysqlStore struct {
+       db          *gorm.DB
+       kind        model.ResourceKind
+       indexers    cache.Indexers
+       indexerLock sync.RWMutex
+       logger      logr.Logger

Review Comment:
   Please use the logger defined in pkg/core/logger to unified the format of 
log.



##########
pkg/store/db/mysql.go:
##########
@@ -17,4 +17,499 @@
 
 package db
 
-// TODO implement memory resource store, refer to GORM https://gorm.io/docs/
+import (
+       "errors"
+       "fmt"
+       "reflect"
+       "sort"
+       "sync"
+
+       storecfg "github.com/apache/dubbo-admin/pkg/config/store"
+       "github.com/go-logr/logr"
+       "gorm.io/driver/mysql"
+       "gorm.io/gorm"
+       "k8s.io/client-go/tools/cache"
+
+       "github.com/apache/dubbo-admin/pkg/common/bizerror"
+       "github.com/apache/dubbo-admin/pkg/common/log"
+       "github.com/apache/dubbo-admin/pkg/core/resource/model"
+       "github.com/apache/dubbo-admin/pkg/core/runtime"
+       "github.com/apache/dubbo-admin/pkg/core/store"
+)
+
+func init() {
+       store.RegisterFactory(&mysqlStoreFactory{})
+}
+
+type mysqlStoreFactory struct{}
+
+var _ store.Factory = &mysqlStoreFactory{}
+
+func (f *mysqlStoreFactory) Support(s storecfg.Type) bool {
+       return s == storecfg.MySQL
+}
+
+func (f *mysqlStoreFactory) New(kind model.ResourceKind, cfg *storecfg.Config) 
(store.ManagedResourceStore, error) {
+       return NewMySQLStore(kind, cfg.Address)
+}
+
+type mysqlStore struct {
+       db          *gorm.DB
+       kind        model.ResourceKind
+       indexers    cache.Indexers
+       indexerLock sync.RWMutex
+       logger      logr.Logger
+}
+
+var _ store.ManagedResourceStore = &mysqlStore{}
+
+func NewMySQLStore(kind model.ResourceKind, address string) 
(store.ManagedResourceStore, error) {
+       db, err := gorm.Open(mysql.Open(address), &gorm.Config{})

Review Comment:
   
这里能否把mysql的连接独立出来?这里上层的使用场景是,对于每一种资源,在逻辑上都是用的不同的store,因此每一种资源初始化时都会调用一次个NewMySQLStore,而这里gorm.Config中没有指定连接池的配置,所以每一次新建都会创建一个新的连接,随着资源的增多,会造成连接的浪费,甚至拖垮数据库。所以这里看能不能把连接的创建挪出来,单独放个地方来进行管理



##########
pkg/store/db/mysql.go:
##########
@@ -17,4 +17,499 @@
 
 package db
 
-// TODO implement memory resource store, refer to GORM https://gorm.io/docs/
+import (
+       "errors"
+       "fmt"
+       "reflect"
+       "sort"
+       "sync"
+
+       storecfg "github.com/apache/dubbo-admin/pkg/config/store"
+       "github.com/go-logr/logr"
+       "gorm.io/driver/mysql"
+       "gorm.io/gorm"
+       "k8s.io/client-go/tools/cache"
+
+       "github.com/apache/dubbo-admin/pkg/common/bizerror"
+       "github.com/apache/dubbo-admin/pkg/common/log"
+       "github.com/apache/dubbo-admin/pkg/core/resource/model"
+       "github.com/apache/dubbo-admin/pkg/core/runtime"
+       "github.com/apache/dubbo-admin/pkg/core/store"
+)
+
+func init() {
+       store.RegisterFactory(&mysqlStoreFactory{})
+}
+
+type mysqlStoreFactory struct{}
+
+var _ store.Factory = &mysqlStoreFactory{}
+
+func (f *mysqlStoreFactory) Support(s storecfg.Type) bool {
+       return s == storecfg.MySQL
+}
+
+func (f *mysqlStoreFactory) New(kind model.ResourceKind, cfg *storecfg.Config) 
(store.ManagedResourceStore, error) {
+       return NewMySQLStore(kind, cfg.Address)
+}
+
+type mysqlStore struct {
+       db          *gorm.DB
+       kind        model.ResourceKind
+       indexers    cache.Indexers
+       indexerLock sync.RWMutex
+       logger      logr.Logger
+}
+
+var _ store.ManagedResourceStore = &mysqlStore{}
+
+func NewMySQLStore(kind model.ResourceKind, address string) 
(store.ManagedResourceStore, error) {
+       db, err := gorm.Open(mysql.Open(address), &gorm.Config{})
+       if err != nil {
+               return nil, fmt.Errorf("failed to connect to mysql: %w", err)
+       }
+
+       if err := db.AutoMigrate(&ResourceModel{}); err != nil {
+               return nil, fmt.Errorf("failed to migrate schema: %w", err)
+       }
+
+       return &mysqlStore{
+               db:       db,
+               kind:     kind,
+               indexers: cache.Indexers{},
+               logger:   log.NewLogger(log.InfoLevel).WithName("mysql-store"),
+       }, nil
+}
+
+func (ms *mysqlStore) Init(_ runtime.BuilderContext) error {
+       return nil
+}
+
+func (ms *mysqlStore) Start(_ runtime.Runtime, _ <-chan struct{}) error {
+       return nil
+}
+
+func (ms *mysqlStore) Add(obj interface{}) error {
+       resource, ok := obj.(model.Resource)
+       if !ok {
+               return bizerror.NewAssertionError("Resource", 
reflect.TypeOf(obj).Name())
+       }
+
+       if resource.ResourceKind() != ms.kind {
+               return fmt.Errorf("resource kind mismatch: expected %s, got 
%s", ms.kind, resource.ResourceKind())
+       }
+
+       var count int64
+       err := ms.db.Model(&ResourceModel{}).
+               Where("resource_key = ?", resource.ResourceKey()).
+               Count(&count).Error
+       if err != nil {
+               return err
+       }
+       if count > 0 {
+               return store.ErrorResourceAlreadyExists(
+                       resource.ResourceKind().ToString(),
+                       resource.ResourceMeta().Name,
+                       resource.MeshName(),
+               )
+       }
+
+       m, err := FromResource(resource)
+       if err != nil {
+               return err
+       }
+
+       return ms.db.Create(m).Error
+}
+
+func (ms *mysqlStore) Update(obj interface{}) error {
+       resource, ok := obj.(model.Resource)
+       if !ok {
+               return bizerror.NewAssertionError("Resource", 
reflect.TypeOf(obj).Name())
+       }
+
+       if resource.ResourceKind() != ms.kind {
+               return fmt.Errorf("resource kind mismatch: expected %s, got 
%s", ms.kind, resource.ResourceKind())
+       }
+
+       m, err := FromResource(resource)
+       if err != nil {
+               return err
+       }
+
+       result := ms.db.Model(&ResourceModel{}).
+               Where("resource_key = ?", resource.ResourceKey()).
+               Updates(map[string]interface{}{
+                       "data":       m.Data,
+                       "updated_at": m.UpdatedAt,
+               })
+
+       if result.Error != nil {
+               return result.Error
+       }
+
+       if result.RowsAffected == 0 {
+               return store.ErrorResourceNotFound(
+                       resource.ResourceKind().ToString(),
+                       resource.ResourceMeta().Name,
+                       resource.MeshName(),
+               )
+       }
+
+       return nil
+}
+
+func (ms *mysqlStore) Delete(obj interface{}) error {
+       resource, ok := obj.(model.Resource)
+       if !ok {
+               return bizerror.NewAssertionError("Resource", 
reflect.TypeOf(obj).Name())
+       }
+
+       result := ms.db.Where("resource_key = ?", resource.ResourceKey()).
+               Delete(&ResourceModel{})
+
+       if result.Error != nil {
+               return result.Error
+       }
+
+       if result.RowsAffected == 0 {
+               return store.ErrorResourceNotFound(
+                       resource.ResourceKind().ToString(),
+                       resource.ResourceMeta().Name,
+                       resource.MeshName(),
+               )
+       }
+
+       return nil
+}
+
+func (ms *mysqlStore) List() []interface{} {
+       var models []ResourceModel
+       if err := ms.db.Where("resource_kind = ?", 
ms.kind.ToString()).Find(&models).Error; err != nil {
+               ms.logger.Error(err, "failed to list resources")
+               return []interface{}{}
+       }
+
+       result := make([]interface{}, 0, len(models))
+       for _, m := range models {
+               resource, err := m.ToResource()
+               if err != nil {
+                       ms.logger.Error(err, "failed to deserialize resource")
+                       continue
+               }
+               result = append(result, resource)
+       }
+       return result
+}
+
+func (ms *mysqlStore) ListKeys() []string {
+       var keys []string
+       ms.db.Model(&ResourceModel{}).
+               Where("resource_kind = ?", ms.kind.ToString()).
+               Pluck("resource_key", &keys)
+       return keys
+}
+
+func (ms *mysqlStore) Get(obj interface{}) (item interface{}, exists bool, err 
error) {
+       resource, ok := obj.(model.Resource)
+       if !ok {
+               return nil, false, bizerror.NewAssertionError("Resource", 
reflect.TypeOf(obj).Name())
+       }
+       return ms.GetByKey(resource.ResourceKey())
+}
+
+func (ms *mysqlStore) GetByKey(key string) (item interface{}, exists bool, err 
error) {
+       var m ResourceModel
+       result := ms.db.Where("resource_key = ? AND resource_kind = ?", key, 
ms.kind.ToString()).
+               First(&m)
+
+       if result.Error != nil {
+               if errors.Is(result.Error, gorm.ErrRecordNotFound) {
+                       return nil, false, nil
+               }
+               return nil, false, result.Error
+       }
+
+       resource, err := m.ToResource()
+       if err != nil {
+               return nil, false, err
+       }
+
+       return resource, true, nil
+}
+
+func (ms *mysqlStore) Replace(list []interface{}, _ string) error {
+       return ms.db.Transaction(func(tx *gorm.DB) error {
+               if err := tx.Where("resource_kind = ?", 
ms.kind.ToString()).Delete(&ResourceModel{}).Error; err != nil {
+                       return err
+               }
+
+               for _, obj := range list {
+                       resource, ok := obj.(model.Resource)
+                       if !ok {
+                               return bizerror.NewAssertionError("Resource", 
reflect.TypeOf(obj).Name())
+                       }
+
+                       m, err := FromResource(resource)
+                       if err != nil {
+                               return err
+                       }
+
+                       if err := tx.Create(m).Error; err != nil {

Review Comment:
   suggestion: 对于列表,用批量插入的方式效率会高很多



##########
pkg/store/db/mysql.go:
##########
@@ -17,4 +17,499 @@
 
 package db
 
-// TODO implement memory resource store, refer to GORM https://gorm.io/docs/
+import (
+       "errors"
+       "fmt"
+       "reflect"
+       "sort"
+       "sync"
+
+       storecfg "github.com/apache/dubbo-admin/pkg/config/store"
+       "github.com/go-logr/logr"
+       "gorm.io/driver/mysql"
+       "gorm.io/gorm"
+       "k8s.io/client-go/tools/cache"
+
+       "github.com/apache/dubbo-admin/pkg/common/bizerror"
+       "github.com/apache/dubbo-admin/pkg/common/log"
+       "github.com/apache/dubbo-admin/pkg/core/resource/model"
+       "github.com/apache/dubbo-admin/pkg/core/runtime"
+       "github.com/apache/dubbo-admin/pkg/core/store"
+)
+
+func init() {
+       store.RegisterFactory(&mysqlStoreFactory{})
+}
+
+type mysqlStoreFactory struct{}
+
+var _ store.Factory = &mysqlStoreFactory{}
+
+func (f *mysqlStoreFactory) Support(s storecfg.Type) bool {
+       return s == storecfg.MySQL
+}
+
+func (f *mysqlStoreFactory) New(kind model.ResourceKind, cfg *storecfg.Config) 
(store.ManagedResourceStore, error) {
+       return NewMySQLStore(kind, cfg.Address)
+}
+
+type mysqlStore struct {
+       db          *gorm.DB
+       kind        model.ResourceKind
+       indexers    cache.Indexers
+       indexerLock sync.RWMutex
+       logger      logr.Logger
+}
+
+var _ store.ManagedResourceStore = &mysqlStore{}
+
+func NewMySQLStore(kind model.ResourceKind, address string) 
(store.ManagedResourceStore, error) {
+       db, err := gorm.Open(mysql.Open(address), &gorm.Config{})
+       if err != nil {
+               return nil, fmt.Errorf("failed to connect to mysql: %w", err)
+       }
+
+       if err := db.AutoMigrate(&ResourceModel{}); err != nil {
+               return nil, fmt.Errorf("failed to migrate schema: %w", err)
+       }
+
+       return &mysqlStore{
+               db:       db,
+               kind:     kind,
+               indexers: cache.Indexers{},
+               logger:   log.NewLogger(log.InfoLevel).WithName("mysql-store"),
+       }, nil
+}
+
+func (ms *mysqlStore) Init(_ runtime.BuilderContext) error {
+       return nil
+}
+
+func (ms *mysqlStore) Start(_ runtime.Runtime, _ <-chan struct{}) error {
+       return nil
+}
+
+func (ms *mysqlStore) Add(obj interface{}) error {
+       resource, ok := obj.(model.Resource)
+       if !ok {
+               return bizerror.NewAssertionError("Resource", 
reflect.TypeOf(obj).Name())
+       }
+
+       if resource.ResourceKind() != ms.kind {
+               return fmt.Errorf("resource kind mismatch: expected %s, got 
%s", ms.kind, resource.ResourceKind())
+       }
+
+       var count int64
+       err := ms.db.Model(&ResourceModel{}).
+               Where("resource_key = ?", resource.ResourceKey()).
+               Count(&count).Error
+       if err != nil {
+               return err
+       }
+       if count > 0 {
+               return store.ErrorResourceAlreadyExists(
+                       resource.ResourceKind().ToString(),
+                       resource.ResourceMeta().Name,
+                       resource.MeshName(),
+               )
+       }
+
+       m, err := FromResource(resource)
+       if err != nil {
+               return err
+       }
+
+       return ms.db.Create(m).Error
+}
+
+func (ms *mysqlStore) Update(obj interface{}) error {
+       resource, ok := obj.(model.Resource)
+       if !ok {
+               return bizerror.NewAssertionError("Resource", 
reflect.TypeOf(obj).Name())
+       }
+
+       if resource.ResourceKind() != ms.kind {
+               return fmt.Errorf("resource kind mismatch: expected %s, got 
%s", ms.kind, resource.ResourceKind())
+       }
+
+       m, err := FromResource(resource)
+       if err != nil {
+               return err
+       }
+
+       result := ms.db.Model(&ResourceModel{}).
+               Where("resource_key = ?", resource.ResourceKey()).
+               Updates(map[string]interface{}{
+                       "data":       m.Data,
+                       "updated_at": m.UpdatedAt,
+               })
+
+       if result.Error != nil {
+               return result.Error
+       }
+
+       if result.RowsAffected == 0 {
+               return store.ErrorResourceNotFound(
+                       resource.ResourceKind().ToString(),
+                       resource.ResourceMeta().Name,
+                       resource.MeshName(),
+               )
+       }
+
+       return nil
+}
+
+func (ms *mysqlStore) Delete(obj interface{}) error {
+       resource, ok := obj.(model.Resource)
+       if !ok {
+               return bizerror.NewAssertionError("Resource", 
reflect.TypeOf(obj).Name())
+       }
+
+       result := ms.db.Where("resource_key = ?", resource.ResourceKey()).
+               Delete(&ResourceModel{})
+
+       if result.Error != nil {
+               return result.Error
+       }
+
+       if result.RowsAffected == 0 {
+               return store.ErrorResourceNotFound(
+                       resource.ResourceKind().ToString(),
+                       resource.ResourceMeta().Name,
+                       resource.MeshName(),
+               )
+       }
+
+       return nil
+}
+
+func (ms *mysqlStore) List() []interface{} {
+       var models []ResourceModel
+       if err := ms.db.Where("resource_kind = ?", 
ms.kind.ToString()).Find(&models).Error; err != nil {
+               ms.logger.Error(err, "failed to list resources")
+               return []interface{}{}
+       }
+
+       result := make([]interface{}, 0, len(models))
+       for _, m := range models {
+               resource, err := m.ToResource()
+               if err != nil {
+                       ms.logger.Error(err, "failed to deserialize resource")
+                       continue
+               }
+               result = append(result, resource)
+       }
+       return result
+}
+
+func (ms *mysqlStore) ListKeys() []string {
+       var keys []string
+       ms.db.Model(&ResourceModel{}).
+               Where("resource_kind = ?", ms.kind.ToString()).
+               Pluck("resource_key", &keys)
+       return keys
+}
+
+func (ms *mysqlStore) Get(obj interface{}) (item interface{}, exists bool, err 
error) {
+       resource, ok := obj.(model.Resource)
+       if !ok {
+               return nil, false, bizerror.NewAssertionError("Resource", 
reflect.TypeOf(obj).Name())
+       }
+       return ms.GetByKey(resource.ResourceKey())
+}
+
+func (ms *mysqlStore) GetByKey(key string) (item interface{}, exists bool, err 
error) {
+       var m ResourceModel
+       result := ms.db.Where("resource_key = ? AND resource_kind = ?", key, 
ms.kind.ToString()).
+               First(&m)
+
+       if result.Error != nil {
+               if errors.Is(result.Error, gorm.ErrRecordNotFound) {
+                       return nil, false, nil
+               }
+               return nil, false, result.Error
+       }
+
+       resource, err := m.ToResource()
+       if err != nil {
+               return nil, false, err
+       }
+
+       return resource, true, nil
+}
+
+func (ms *mysqlStore) Replace(list []interface{}, _ string) error {
+       return ms.db.Transaction(func(tx *gorm.DB) error {
+               if err := tx.Where("resource_kind = ?", 
ms.kind.ToString()).Delete(&ResourceModel{}).Error; err != nil {
+                       return err
+               }
+
+               for _, obj := range list {
+                       resource, ok := obj.(model.Resource)
+                       if !ok {
+                               return bizerror.NewAssertionError("Resource", 
reflect.TypeOf(obj).Name())
+                       }
+
+                       m, err := FromResource(resource)
+                       if err != nil {
+                               return err
+                       }
+
+                       if err := tx.Create(m).Error; err != nil {
+                               return err
+                       }
+               }
+
+               return nil
+       })
+}
+
+func (ms *mysqlStore) Resync() error {
+       return nil
+}
+
+func (ms *mysqlStore) Index(indexName string, obj interface{}) ([]interface{}, 
error) {
+       ms.indexerLock.RLock()
+       indexFunc, exists := ms.indexers[indexName]
+       ms.indexerLock.RUnlock()
+
+       if !exists {
+               return nil, fmt.Errorf("index %s does not exist", indexName)
+       }
+
+       indexValues, err := indexFunc(obj)
+       if err != nil {
+               return nil, err
+       }
+
+       if len(indexValues) == 0 {
+               return []interface{}{}, nil
+       }
+
+       return ms.findByIndex(indexName, indexValues[0])
+}
+
+func (ms *mysqlStore) IndexKeys(indexName, indexedValue string) ([]string, 
error) {
+       ms.indexerLock.RLock()
+       _, exists := ms.indexers[indexName]
+       ms.indexerLock.RUnlock()
+
+       if !exists {
+               return nil, fmt.Errorf("index %s does not exist", indexName)
+       }
+
+       resources, err := ms.findByIndex(indexName, indexedValue)
+       if err != nil {
+               return nil, err
+       }
+
+       keys := make([]string, 0, len(resources))
+       for _, obj := range resources {
+               if resource, ok := obj.(model.Resource); ok {
+                       keys = append(keys, resource.ResourceKey())
+               }
+       }
+
+       return keys, nil
+}
+
+func (ms *mysqlStore) ListIndexFuncValues(indexName string) []string {
+       ms.indexerLock.RLock()
+       _, exists := ms.indexers[indexName]
+       ms.indexerLock.RUnlock()
+
+       if !exists {
+               return []string{}
+       }
+
+       resources := ms.List()
+       valueSet := make(map[string]struct{})
+
+       for _, obj := range resources {
+               if indexFunc, ok := ms.indexers[indexName]; ok {
+                       values, err := indexFunc(obj)
+                       if err == nil {
+                               for _, value := range values {
+                                       valueSet[value] = struct{}{}
+                               }
+                       }
+               }
+       }
+
+       result := make([]string, 0, len(valueSet))
+       for value := range valueSet {
+               result = append(result, value)
+       }
+
+       return result
+}
+
+func (ms *mysqlStore) ByIndex(indexName, indexedValue string) ([]interface{}, 
error) {
+       ms.indexerLock.RLock()
+       _, exists := ms.indexers[indexName]
+       ms.indexerLock.RUnlock()
+
+       if !exists {
+               return nil, fmt.Errorf("index %s does not exist", indexName)
+       }
+
+       return ms.findByIndex(indexName, indexedValue)
+}
+
+func (ms *mysqlStore) GetIndexers() cache.Indexers {
+       ms.indexerLock.RLock()
+       defer ms.indexerLock.RUnlock()
+
+       result := make(cache.Indexers, len(ms.indexers))
+       for k, v := range ms.indexers {
+               result[k] = v
+       }
+       return result
+}
+
+func (ms *mysqlStore) AddIndexers(newIndexers cache.Indexers) error {
+       ms.indexerLock.Lock()
+       defer ms.indexerLock.Unlock()
+
+       for name, indexFunc := range newIndexers {
+               if _, exists := ms.indexers[name]; exists {
+                       return fmt.Errorf("indexer %s already exists", name)
+               }
+               ms.indexers[name] = indexFunc
+       }
+
+       return nil
+}
+
+func (ms *mysqlStore) GetByKeys(keys []string) ([]model.Resource, error) {
+       if len(keys) == 0 {
+               return []model.Resource{}, nil
+       }
+
+       var models []ResourceModel
+       err := ms.db.Where("resource_key IN ? AND resource_kind = ?", keys, 
ms.kind.ToString()).
+               Find(&models).Error
+       if err != nil {
+               return nil, err
+       }
+
+       resources := make([]model.Resource, 0, len(models))
+       for _, m := range models {
+               resource, err := m.ToResource()
+               if err != nil {
+                       return nil, err
+               }
+               resources = append(resources, resource)
+       }
+
+       return resources, nil
+}
+
+func (ms *mysqlStore) ListByIndexes(indexes map[string]string) 
([]model.Resource, error) {
+       keys, err := ms.getKeysByIndexes(indexes)
+       if err != nil {
+               return nil, err
+       }
+
+       resources, err := ms.GetByKeys(keys)
+       if err != nil {
+               return nil, err
+       }
+
+       sort.Slice(resources, func(i, j int) bool {
+               return resources[i].ResourceKey() < resources[j].ResourceKey()
+       })
+
+       return resources, nil
+}
+
+func (ms *mysqlStore) PageListByIndexes(indexes map[string]string, pq 
model.PageReq) (*model.PageData[model.Resource], error) {
+       keys, err := ms.getKeysByIndexes(indexes)
+       if err != nil {
+               return nil, err
+       }
+
+       sort.Strings(keys)
+       total := len(keys)
+
+       if pq.PageOffset >= total {
+               return model.NewPageData(total, pq.PageOffset, pq.PageSize, 
[]model.Resource{}), nil
+       }
+
+       end := pq.PageOffset + pq.PageSize
+       if end > total {
+               end = total
+       }
+
+       pageKeys := keys[pq.PageOffset:end]
+       resources, err := ms.GetByKeys(pageKeys)
+       if err != nil {
+               return nil, err
+       }
+
+       return model.NewPageData(total, pq.PageOffset, pq.PageSize, resources), 
nil
+}
+
+func (ms *mysqlStore) findByIndex(indexName, indexedValue string) 
([]interface{}, error) {

Review Comment:
   correct: 这里的index应该是内存维护的一个索引机制,这个索引简单来说就是一个map[string][]string, key->value 
的映射关系为indexedValue->set of ResourceKey/ID。举例:插入一个resource(appName="appA", 
resourceKey="RA"), 
且该store有appName的索引,这个时候应该在插入/更新/删除后,执行indexFunc,得到IndexValue,然后更新对应的索引,这时候里面的值就是{"appA":
 
["RA"]]}。通过索引来查询的过程就是,通过indexValue直接到map中取到符合条件的resourcekeys,然后再去DB中通过getByKeys一次性捞出来,这个过程效率就非常高。



##########
pkg/store/db/model.go:
##########
@@ -0,0 +1,67 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package db
+
+import (
+       "encoding/json"
+       "time"
+
+       "github.com/apache/dubbo-admin/pkg/core/resource/model"
+)
+
+type ResourceModel struct {
+       ID           uint      `gorm:"primarykey"`
+       ResourceKey  string    `gorm:"uniqueIndex;not null"`
+       ResourceKind string    `gorm:"index;not null"`
+       Name         string    `gorm:"index;not null"`
+       Mesh         string    `gorm:"index;not null"`
+       Data         []byte    `gorm:"type:text;not null"`
+       CreatedAt    time.Time `gorm:"autoCreateTime"`
+       UpdatedAt    time.Time `gorm:"autoUpdateTime"`
+}
+
+func (*ResourceModel) TableName() string {
+       return "resources"

Review Comment:
   suggestion: 
不同的resource放到不同的table,这样读写时能够缓解单表压力;不同类型的resource可能name一样,会导致唯一键冲突;排查问题时也能够更快地定位到某一个表



##########
pkg/store/db/postgres.go:
##########


Review Comment:
   suggestion: 这里如果把gorm的连接统一掉,是不是代码就和mysql的一样?



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to